blob: ef409367bfd66ffadc38704d2322efe86a0c7947 [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00002# Copyright (c) 2011 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
6"""This is a simple HTTP server used for testing Chrome.
7
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
initial.commit94958cf2008-07-26 22:42:52 +000021import optparse
22import os
23import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000024import select
akalin@chromium.org18e34882010-11-26 07:10:41 +000025import simplejson
initial.commit94958cf2008-07-26 22:42:52 +000026import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import socket
initial.commit94958cf2008-07-26 22:42:52 +000028import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000029import struct
initial.commit94958cf2008-07-26 22:42:52 +000030import time
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000031import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000032import warnings
33
34# Ignore deprecation warnings, they make our output more cluttered.
35warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000036
37import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000038import tlslite
39import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000040
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000041try:
42 import hashlib
43 _new_md5 = hashlib.md5
44except ImportError:
45 import md5
46 _new_md5 = md5.new
47
davidben@chromium.org06fcf202010-09-22 18:15:23 +000048if sys.platform == 'win32':
49 import msvcrt
50
maruel@chromium.org756cf982009-03-05 12:46:38 +000051SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000052SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000053SERVER_SYNC = 2
initial.commit94958cf2008-07-26 22:42:52 +000054
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000055# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000056debug_output = sys.stderr
57def debug(str):
58 debug_output.write(str + "\n")
59 debug_output.flush()
60
61class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
62 """This is a specialization of of BaseHTTPServer to allow it
63 to be exited cleanly (by setting its "stop" member to True)."""
64
65 def serve_forever(self):
66 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000067 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000068 while not self.stop:
69 self.handle_request()
70 self.socket.close()
71
72class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
73 """This is a specialization of StoppableHTTPerver that add https support."""
74
davidben@chromium.org31282a12010-08-07 01:10:02 +000075 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000076 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000077 s = open(cert_path).read()
78 x509 = tlslite.api.X509()
79 x509.parse(s)
80 self.cert_chain = tlslite.api.X509CertChain([x509])
81 s = open(cert_path).read()
82 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000083 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000084 self.ssl_client_cas = []
85 for ca_file in ssl_client_cas:
86 s = open(ca_file).read()
87 x509 = tlslite.api.X509()
88 x509.parse(s)
89 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000090 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
91 if ssl_bulk_ciphers is not None:
92 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000093
94 self.session_cache = tlslite.api.SessionCache()
95 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
96
97 def handshake(self, tlsConnection):
98 """Creates the SSL connection."""
99 try:
100 tlsConnection.handshakeServer(certChain=self.cert_chain,
101 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000102 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000103 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000104 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000105 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000106 tlsConnection.ignoreAbruptClose = True
107 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000108 except tlslite.api.TLSAbruptCloseError:
109 # Ignore abrupt close.
110 return True
initial.commit94958cf2008-07-26 22:42:52 +0000111 except tlslite.api.TLSError, error:
112 print "Handshake failure:", str(error)
113 return False
114
akalin@chromium.org154bb132010-11-12 02:20:27 +0000115
116class SyncHTTPServer(StoppableHTTPServer):
117 """An HTTP server that handles sync commands."""
118
119 def __init__(self, server_address, request_handler_class):
120 # We import here to avoid pulling in chromiumsync's dependencies
121 # unless strictly necessary.
122 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000123 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000124 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000125 self._sync_handler = chromiumsync.TestServer()
126 self._xmpp_socket_map = {}
127 self._xmpp_server = xmppserver.XmppServer(
128 self._xmpp_socket_map, ('localhost', 0))
129 self.xmpp_port = self._xmpp_server.getsockname()[1]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000130
131 def HandleCommand(self, query, raw_request):
132 return self._sync_handler.HandleCommand(query, raw_request)
133
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000134 def HandleRequestNoBlock(self):
135 """Handles a single request.
136
137 Copied from SocketServer._handle_request_noblock().
138 """
139 try:
140 request, client_address = self.get_request()
141 except socket.error:
142 return
143 if self.verify_request(request, client_address):
144 try:
145 self.process_request(request, client_address)
146 except:
147 self.handle_error(request, client_address)
148 self.close_request(request)
149
150 def serve_forever(self):
151 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
152 """
153
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000154 def HandleXmppSocket(fd, socket_map, handler):
155 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000156
157 Adapted from asyncore.read() et al.
158 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000159 xmpp_connection = socket_map.get(fd)
160 # This could happen if a previous handler call caused fd to get
161 # removed from socket_map.
162 if xmpp_connection is None:
163 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000164 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000165 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000166 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
167 raise
168 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000169 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000170
171 while True:
172 read_fds = [ self.fileno() ]
173 write_fds = []
174 exceptional_fds = []
175
176 for fd, xmpp_connection in self._xmpp_socket_map.items():
177 is_r = xmpp_connection.readable()
178 is_w = xmpp_connection.writable()
179 if is_r:
180 read_fds.append(fd)
181 if is_w:
182 write_fds.append(fd)
183 if is_r or is_w:
184 exceptional_fds.append(fd)
185
186 try:
187 read_fds, write_fds, exceptional_fds = (
188 select.select(read_fds, write_fds, exceptional_fds))
189 except select.error, err:
190 if err.args[0] != errno.EINTR:
191 raise
192 else:
193 continue
194
195 for fd in read_fds:
196 if fd == self.fileno():
197 self.HandleRequestNoBlock()
198 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000199 HandleXmppSocket(fd, self._xmpp_socket_map,
200 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000201
202 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000203 HandleXmppSocket(fd, self._xmpp_socket_map,
204 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000205
206 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000207 HandleXmppSocket(fd, self._xmpp_socket_map,
208 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000209
akalin@chromium.org154bb132010-11-12 02:20:27 +0000210
211class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
212
213 def __init__(self, request, client_address, socket_server,
214 connect_handlers, get_handlers, post_handlers, put_handlers):
215 self._connect_handlers = connect_handlers
216 self._get_handlers = get_handlers
217 self._post_handlers = post_handlers
218 self._put_handlers = put_handlers
219 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
220 self, request, client_address, socket_server)
221
222 def log_request(self, *args, **kwargs):
223 # Disable request logging to declutter test log output.
224 pass
225
226 def _ShouldHandleRequest(self, handler_name):
227 """Determines if the path can be handled by the handler.
228
229 We consider a handler valid if the path begins with the
230 handler name. It can optionally be followed by "?*", "/*".
231 """
232
233 pattern = re.compile('%s($|\?|/).*' % handler_name)
234 return pattern.match(self.path)
235
236 def do_CONNECT(self):
237 for handler in self._connect_handlers:
238 if handler():
239 return
240
241 def do_GET(self):
242 for handler in self._get_handlers:
243 if handler():
244 return
245
246 def do_POST(self):
247 for handler in self._post_handlers:
248 if handler():
249 return
250
251 def do_PUT(self):
252 for handler in self._put_handlers:
253 if handler():
254 return
255
256
257class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000258
259 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000260 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000261 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000262 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000263 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000264 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000265 self.NoCacheMaxAgeTimeHandler,
266 self.NoCacheTimeHandler,
267 self.CacheTimeHandler,
268 self.CacheExpiresHandler,
269 self.CacheProxyRevalidateHandler,
270 self.CachePrivateHandler,
271 self.CachePublicHandler,
272 self.CacheSMaxAgeHandler,
273 self.CacheMustRevalidateHandler,
274 self.CacheMustRevalidateMaxAgeHandler,
275 self.CacheNoStoreHandler,
276 self.CacheNoStoreMaxAgeHandler,
277 self.CacheNoTransformHandler,
278 self.DownloadHandler,
279 self.DownloadFinishHandler,
280 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000281 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000282 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000283 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000284 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000285 self.AuthBasicHandler,
286 self.AuthDigestHandler,
287 self.SlowServerHandler,
288 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000289 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000290 self.ServerRedirectHandler,
291 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000292 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000293 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000294 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000295 self.EchoTitleHandler,
296 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000297 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000298 self.DeviceManagementHandler] + get_handlers
299 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000300 self.EchoTitleHandler,
301 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000302 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000303
maruel@google.come250a9b2009-03-10 17:39:46 +0000304 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000305 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000306 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000307 'gif': 'image/gif',
308 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000309 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000310 'pdf' : 'application/pdf',
311 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000312 }
initial.commit94958cf2008-07-26 22:42:52 +0000313 self._default_mime_type = 'text/html'
314
akalin@chromium.org154bb132010-11-12 02:20:27 +0000315 BasePageHandler.__init__(self, request, client_address, socket_server,
316 connect_handlers, get_handlers, post_handlers,
317 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000318
initial.commit94958cf2008-07-26 22:42:52 +0000319 def GetMIMETypeFromName(self, file_name):
320 """Returns the mime type for the specified file_name. So far it only looks
321 at the file extension."""
322
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000323 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000324 if len(extension) == 0:
325 # no extension.
326 return self._default_mime_type
327
ericroman@google.comc17ca532009-05-07 03:51:05 +0000328 # extension starts with a dot, so we need to remove it
329 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000330
initial.commit94958cf2008-07-26 22:42:52 +0000331 def NoCacheMaxAgeTimeHandler(self):
332 """This request handler yields a page with the title set to the current
333 system time, and no caching requested."""
334
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000335 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000336 return False
337
338 self.send_response(200)
339 self.send_header('Cache-Control', 'max-age=0')
340 self.send_header('Content-type', 'text/html')
341 self.end_headers()
342
maruel@google.come250a9b2009-03-10 17:39:46 +0000343 self.wfile.write('<html><head><title>%s</title></head></html>' %
344 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000345
346 return True
347
348 def NoCacheTimeHandler(self):
349 """This request handler yields a page with the title set to the current
350 system time, and no caching requested."""
351
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000352 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000353 return False
354
355 self.send_response(200)
356 self.send_header('Cache-Control', 'no-cache')
357 self.send_header('Content-type', 'text/html')
358 self.end_headers()
359
maruel@google.come250a9b2009-03-10 17:39:46 +0000360 self.wfile.write('<html><head><title>%s</title></head></html>' %
361 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000362
363 return True
364
365 def CacheTimeHandler(self):
366 """This request handler yields a page with the title set to the current
367 system time, and allows caching for one minute."""
368
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000369 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000370 return False
371
372 self.send_response(200)
373 self.send_header('Cache-Control', 'max-age=60')
374 self.send_header('Content-type', 'text/html')
375 self.end_headers()
376
maruel@google.come250a9b2009-03-10 17:39:46 +0000377 self.wfile.write('<html><head><title>%s</title></head></html>' %
378 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000379
380 return True
381
382 def CacheExpiresHandler(self):
383 """This request handler yields a page with the title set to the current
384 system time, and set the page to expire on 1 Jan 2099."""
385
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000386 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000387 return False
388
389 self.send_response(200)
390 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
391 self.send_header('Content-type', 'text/html')
392 self.end_headers()
393
maruel@google.come250a9b2009-03-10 17:39:46 +0000394 self.wfile.write('<html><head><title>%s</title></head></html>' %
395 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000396
397 return True
398
399 def CacheProxyRevalidateHandler(self):
400 """This request handler yields a page with the title set to the current
401 system time, and allows caching for 60 seconds"""
402
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000403 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000404 return False
405
406 self.send_response(200)
407 self.send_header('Content-type', 'text/html')
408 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
409 self.end_headers()
410
maruel@google.come250a9b2009-03-10 17:39:46 +0000411 self.wfile.write('<html><head><title>%s</title></head></html>' %
412 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000413
414 return True
415
416 def CachePrivateHandler(self):
417 """This request handler yields a page with the title set to the current
418 system time, and allows caching for 5 seconds."""
419
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000420 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000421 return False
422
423 self.send_response(200)
424 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000425 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000426 self.end_headers()
427
maruel@google.come250a9b2009-03-10 17:39:46 +0000428 self.wfile.write('<html><head><title>%s</title></head></html>' %
429 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000430
431 return True
432
433 def CachePublicHandler(self):
434 """This request handler yields a page with the title set to the current
435 system time, and allows caching for 5 seconds."""
436
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000437 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000438 return False
439
440 self.send_response(200)
441 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000442 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.end_headers()
444
maruel@google.come250a9b2009-03-10 17:39:46 +0000445 self.wfile.write('<html><head><title>%s</title></head></html>' %
446 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000447
448 return True
449
450 def CacheSMaxAgeHandler(self):
451 """This request handler yields a page with the title set to the current
452 system time, and does not allow for caching."""
453
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000454 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000455 return False
456
457 self.send_response(200)
458 self.send_header('Content-type', 'text/html')
459 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
460 self.end_headers()
461
maruel@google.come250a9b2009-03-10 17:39:46 +0000462 self.wfile.write('<html><head><title>%s</title></head></html>' %
463 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000464
465 return True
466
467 def CacheMustRevalidateHandler(self):
468 """This request handler yields a page with the title set to the current
469 system time, and does not allow caching."""
470
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000471 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000472 return False
473
474 self.send_response(200)
475 self.send_header('Content-type', 'text/html')
476 self.send_header('Cache-Control', 'must-revalidate')
477 self.end_headers()
478
maruel@google.come250a9b2009-03-10 17:39:46 +0000479 self.wfile.write('<html><head><title>%s</title></head></html>' %
480 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000481
482 return True
483
484 def CacheMustRevalidateMaxAgeHandler(self):
485 """This request handler yields a page with the title set to the current
486 system time, and does not allow caching event though max-age of 60
487 seconds is specified."""
488
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000489 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000490 return False
491
492 self.send_response(200)
493 self.send_header('Content-type', 'text/html')
494 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
495 self.end_headers()
496
maruel@google.come250a9b2009-03-10 17:39:46 +0000497 self.wfile.write('<html><head><title>%s</title></head></html>' %
498 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000499
500 return True
501
initial.commit94958cf2008-07-26 22:42:52 +0000502 def CacheNoStoreHandler(self):
503 """This request handler yields a page with the title set to the current
504 system time, and does not allow the page to be stored."""
505
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000506 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000507 return False
508
509 self.send_response(200)
510 self.send_header('Content-type', 'text/html')
511 self.send_header('Cache-Control', 'no-store')
512 self.end_headers()
513
maruel@google.come250a9b2009-03-10 17:39:46 +0000514 self.wfile.write('<html><head><title>%s</title></head></html>' %
515 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000516
517 return True
518
519 def CacheNoStoreMaxAgeHandler(self):
520 """This request handler yields a page with the title set to the current
521 system time, and does not allow the page to be stored even though max-age
522 of 60 seconds is specified."""
523
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000524 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000525 return False
526
527 self.send_response(200)
528 self.send_header('Content-type', 'text/html')
529 self.send_header('Cache-Control', 'max-age=60, no-store')
530 self.end_headers()
531
maruel@google.come250a9b2009-03-10 17:39:46 +0000532 self.wfile.write('<html><head><title>%s</title></head></html>' %
533 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000534
535 return True
536
537
538 def CacheNoTransformHandler(self):
539 """This request handler yields a page with the title set to the current
540 system time, and does not allow the content to transformed during
541 user-agent caching"""
542
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000543 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000544 return False
545
546 self.send_response(200)
547 self.send_header('Content-type', 'text/html')
548 self.send_header('Cache-Control', 'no-transform')
549 self.end_headers()
550
maruel@google.come250a9b2009-03-10 17:39:46 +0000551 self.wfile.write('<html><head><title>%s</title></head></html>' %
552 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000553
554 return True
555
556 def EchoHeader(self):
557 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000558 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000559
ananta@chromium.org56812d02011-04-07 17:52:05 +0000560 """This function echoes back the value of a specific request header"""
561 """while allowing caching for 16 hours."""
562 def EchoHeaderCache(self):
563 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000564
565 def EchoHeaderHelper(self, echo_header):
566 """This function echoes back the value of the request header passed in."""
567 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000568 return False
569
570 query_char = self.path.find('?')
571 if query_char != -1:
572 header_name = self.path[query_char+1:]
573
574 self.send_response(200)
575 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000576 if echo_header == '/echoheadercache':
577 self.send_header('Cache-control', 'max-age=60000')
578 else:
579 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000580 # insert a vary header to properly indicate that the cachability of this
581 # request is subject to value of the request header being echoed.
582 if len(header_name) > 0:
583 self.send_header('Vary', header_name)
584 self.end_headers()
585
586 if len(header_name) > 0:
587 self.wfile.write(self.headers.getheader(header_name))
588
589 return True
590
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000591 def ReadRequestBody(self):
592 """This function reads the body of the current HTTP request, handling
593 both plain and chunked transfer encoded requests."""
594
595 if self.headers.getheader('transfer-encoding') != 'chunked':
596 length = int(self.headers.getheader('content-length'))
597 return self.rfile.read(length)
598
599 # Read the request body as chunks.
600 body = ""
601 while True:
602 line = self.rfile.readline()
603 length = int(line, 16)
604 if length == 0:
605 self.rfile.readline()
606 break
607 body += self.rfile.read(length)
608 self.rfile.read(2)
609 return body
610
initial.commit94958cf2008-07-26 22:42:52 +0000611 def EchoHandler(self):
612 """This handler just echoes back the payload of the request, for testing
613 form submission."""
614
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000615 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000616 return False
617
618 self.send_response(200)
619 self.send_header('Content-type', 'text/html')
620 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000621 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000622 return True
623
624 def EchoTitleHandler(self):
625 """This handler is like Echo, but sets the page title to the request."""
626
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000627 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000628 return False
629
630 self.send_response(200)
631 self.send_header('Content-type', 'text/html')
632 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000633 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000634 self.wfile.write('<html><head><title>')
635 self.wfile.write(request)
636 self.wfile.write('</title></head></html>')
637 return True
638
639 def EchoAllHandler(self):
640 """This handler yields a (more) human-readable page listing information
641 about the request header & contents."""
642
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000643 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000644 return False
645
646 self.send_response(200)
647 self.send_header('Content-type', 'text/html')
648 self.end_headers()
649 self.wfile.write('<html><head><style>'
650 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
651 '</style></head><body>'
652 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000653 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000654 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000655
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000656 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000657 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000658 params = cgi.parse_qs(qs, keep_blank_values=1)
659
660 for param in params:
661 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000662
663 self.wfile.write('</pre>')
664
665 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
666
667 self.wfile.write('</body></html>')
668 return True
669
670 def DownloadHandler(self):
671 """This handler sends a downloadable file with or without reporting
672 the size (6K)."""
673
674 if self.path.startswith("/download-unknown-size"):
675 send_length = False
676 elif self.path.startswith("/download-known-size"):
677 send_length = True
678 else:
679 return False
680
681 #
682 # The test which uses this functionality is attempting to send
683 # small chunks of data to the client. Use a fairly large buffer
684 # so that we'll fill chrome's IO buffer enough to force it to
685 # actually write the data.
686 # See also the comments in the client-side of this test in
687 # download_uitest.cc
688 #
689 size_chunk1 = 35*1024
690 size_chunk2 = 10*1024
691
692 self.send_response(200)
693 self.send_header('Content-type', 'application/octet-stream')
694 self.send_header('Cache-Control', 'max-age=0')
695 if send_length:
696 self.send_header('Content-Length', size_chunk1 + size_chunk2)
697 self.end_headers()
698
699 # First chunk of data:
700 self.wfile.write("*" * size_chunk1)
701 self.wfile.flush()
702
703 # handle requests until one of them clears this flag.
704 self.server.waitForDownload = True
705 while self.server.waitForDownload:
706 self.server.handle_request()
707
708 # Second chunk of data:
709 self.wfile.write("*" * size_chunk2)
710 return True
711
712 def DownloadFinishHandler(self):
713 """This handler just tells the server to finish the current download."""
714
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000715 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000716 return False
717
718 self.server.waitForDownload = False
719 self.send_response(200)
720 self.send_header('Content-type', 'text/html')
721 self.send_header('Cache-Control', 'max-age=0')
722 self.end_headers()
723 return True
724
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000725 def _ReplaceFileData(self, data, query_parameters):
726 """Replaces matching substrings in a file.
727
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000728 If the 'replace_text' URL query parameter is present, it is expected to be
729 of the form old_text:new_text, which indicates that any old_text strings in
730 the file are replaced with new_text. Multiple 'replace_text' parameters may
731 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000732
733 If the parameters are not present, |data| is returned.
734 """
735 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000736 replace_text_values = query_dict.get('replace_text', [])
737 for replace_text_value in replace_text_values:
738 replace_text_args = replace_text_value.split(':')
739 if len(replace_text_args) != 2:
740 raise ValueError(
741 'replace_text must be of form old_text:new_text. Actual value: %s' %
742 replace_text_value)
743 old_text_b64, new_text_b64 = replace_text_args
744 old_text = base64.urlsafe_b64decode(old_text_b64)
745 new_text = base64.urlsafe_b64decode(new_text_b64)
746 data = data.replace(old_text, new_text)
747 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000748
initial.commit94958cf2008-07-26 22:42:52 +0000749 def FileHandler(self):
750 """This handler sends the contents of the requested file. Wow, it's like
751 a real webserver!"""
752
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000753 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000754 if not self.path.startswith(prefix):
755 return False
756
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000757 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000758 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000759 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000760
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000761 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
762 sub_path = url_path[len(prefix):]
763 entries = sub_path.split('/')
764 file_path = os.path.join(self.server.data_dir, *entries)
765 if os.path.isdir(file_path):
766 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000767
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000768 if not os.path.isfile(file_path):
769 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000770 self.send_error(404)
771 return True
772
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000773 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000774 data = f.read()
775 f.close()
776
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000777 data = self._ReplaceFileData(data, query)
778
initial.commit94958cf2008-07-26 22:42:52 +0000779 # If file.mock-http-headers exists, it contains the headers we
780 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000781 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000782 if os.path.isfile(headers_path):
783 f = open(headers_path, "r")
784
785 # "HTTP/1.1 200 OK"
786 response = f.readline()
787 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
788 self.send_response(int(status_code))
789
790 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000791 header_values = re.findall('(\S+):\s*(.*)', line)
792 if len(header_values) > 0:
793 # "name: value"
794 name, value = header_values[0]
795 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000796 f.close()
797 else:
798 # Could be more generic once we support mime-type sniffing, but for
799 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000800
801 range = self.headers.get('Range')
802 if range and range.startswith('bytes='):
803 # Note this doesn't handle all valid byte range values (i.e. open ended
804 # ones), just enough for what we needed so far.
805 range = range[6:].split('-')
806 start = int(range[0])
807 end = int(range[1])
808
809 self.send_response(206)
810 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
811 str(len(data))
812 self.send_header('Content-Range', content_range)
813 data = data[start: end + 1]
814 else:
815 self.send_response(200)
816
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000817 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000818 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000819 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000820 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000821 self.end_headers()
822
823 self.wfile.write(data)
824
825 return True
826
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000827 def SetCookieHandler(self):
828 """This handler just sets a cookie, for testing cookie handling."""
829
830 if not self._ShouldHandleRequest("/set-cookie"):
831 return False
832
833 query_char = self.path.find('?')
834 if query_char != -1:
835 cookie_values = self.path[query_char + 1:].split('&')
836 else:
837 cookie_values = ("",)
838 self.send_response(200)
839 self.send_header('Content-type', 'text/html')
840 for cookie_value in cookie_values:
841 self.send_header('Set-Cookie', '%s' % cookie_value)
842 self.end_headers()
843 for cookie_value in cookie_values:
844 self.wfile.write('%s' % cookie_value)
845 return True
846
initial.commit94958cf2008-07-26 22:42:52 +0000847 def AuthBasicHandler(self):
848 """This handler tests 'Basic' authentication. It just sends a page with
849 title 'user/pass' if you succeed."""
850
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000851 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000852 return False
853
854 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000855 expected_password = 'secret'
856 realm = 'testrealm'
857 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000858
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000859 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
860 query_params = cgi.parse_qs(query, True)
861 if 'set-cookie-if-challenged' in query_params:
862 set_cookie_if_challenged = True
863 if 'password' in query_params:
864 expected_password = query_params['password'][0]
865 if 'realm' in query_params:
866 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000867
initial.commit94958cf2008-07-26 22:42:52 +0000868 auth = self.headers.getheader('authorization')
869 try:
870 if not auth:
871 raise Exception('no auth')
872 b64str = re.findall(r'Basic (\S+)', auth)[0]
873 userpass = base64.b64decode(b64str)
874 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000875 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000876 raise Exception('wrong password')
877 except Exception, e:
878 # Authentication failed.
879 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000880 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000881 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000882 if set_cookie_if_challenged:
883 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000884 self.end_headers()
885 self.wfile.write('<html><head>')
886 self.wfile.write('<title>Denied: %s</title>' % e)
887 self.wfile.write('</head><body>')
888 self.wfile.write('auth=%s<p>' % auth)
889 self.wfile.write('b64str=%s<p>' % b64str)
890 self.wfile.write('username: %s<p>' % username)
891 self.wfile.write('userpass: %s<p>' % userpass)
892 self.wfile.write('password: %s<p>' % password)
893 self.wfile.write('You sent:<br>%s<p>' % self.headers)
894 self.wfile.write('</body></html>')
895 return True
896
897 # Authentication successful. (Return a cachable response to allow for
898 # testing cached pages that require authentication.)
899 if_none_match = self.headers.getheader('if-none-match')
900 if if_none_match == "abc":
901 self.send_response(304)
902 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000903 elif url_path.endswith(".gif"):
904 # Using chrome/test/data/google/logo.gif as the test image
905 test_image_path = ['google', 'logo.gif']
906 gif_path = os.path.join(self.server.data_dir, *test_image_path)
907 if not os.path.isfile(gif_path):
908 self.send_error(404)
909 return True
910
911 f = open(gif_path, "rb")
912 data = f.read()
913 f.close()
914
915 self.send_response(200)
916 self.send_header('Content-type', 'image/gif')
917 self.send_header('Cache-control', 'max-age=60000')
918 self.send_header('Etag', 'abc')
919 self.end_headers()
920 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000921 else:
922 self.send_response(200)
923 self.send_header('Content-type', 'text/html')
924 self.send_header('Cache-control', 'max-age=60000')
925 self.send_header('Etag', 'abc')
926 self.end_headers()
927 self.wfile.write('<html><head>')
928 self.wfile.write('<title>%s/%s</title>' % (username, password))
929 self.wfile.write('</head><body>')
930 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000931 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000932 self.wfile.write('</body></html>')
933
934 return True
935
tonyg@chromium.org75054202010-03-31 22:06:10 +0000936 def GetNonce(self, force_reset=False):
937 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000938
tonyg@chromium.org75054202010-03-31 22:06:10 +0000939 This is a fake implementation. A real implementation would only use a given
940 nonce a single time (hence the name n-once). However, for the purposes of
941 unittesting, we don't care about the security of the nonce.
942
943 Args:
944 force_reset: Iff set, the nonce will be changed. Useful for testing the
945 "stale" response.
946 """
947 if force_reset or not self.server.nonce_time:
948 self.server.nonce_time = time.time()
949 return _new_md5('privatekey%s%d' %
950 (self.path, self.server.nonce_time)).hexdigest()
951
952 def AuthDigestHandler(self):
953 """This handler tests 'Digest' authentication.
954
955 It just sends a page with title 'user/pass' if you succeed.
956
957 A stale response is sent iff "stale" is present in the request path.
958 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000959 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000960 return False
961
tonyg@chromium.org75054202010-03-31 22:06:10 +0000962 stale = 'stale' in self.path
963 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000964 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000965 password = 'secret'
966 realm = 'testrealm'
967
968 auth = self.headers.getheader('authorization')
969 pairs = {}
970 try:
971 if not auth:
972 raise Exception('no auth')
973 if not auth.startswith('Digest'):
974 raise Exception('not digest')
975 # Pull out all the name="value" pairs as a dictionary.
976 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
977
978 # Make sure it's all valid.
979 if pairs['nonce'] != nonce:
980 raise Exception('wrong nonce')
981 if pairs['opaque'] != opaque:
982 raise Exception('wrong opaque')
983
984 # Check the 'response' value and make sure it matches our magic hash.
985 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000986 hash_a1 = _new_md5(
987 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000988 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000989 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000990 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000991 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
992 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000993 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000994
995 if pairs['response'] != response:
996 raise Exception('wrong password')
997 except Exception, e:
998 # Authentication failed.
999 self.send_response(401)
1000 hdr = ('Digest '
1001 'realm="%s", '
1002 'domain="/", '
1003 'qop="auth", '
1004 'algorithm=MD5, '
1005 'nonce="%s", '
1006 'opaque="%s"') % (realm, nonce, opaque)
1007 if stale:
1008 hdr += ', stale="TRUE"'
1009 self.send_header('WWW-Authenticate', hdr)
1010 self.send_header('Content-type', 'text/html')
1011 self.end_headers()
1012 self.wfile.write('<html><head>')
1013 self.wfile.write('<title>Denied: %s</title>' % e)
1014 self.wfile.write('</head><body>')
1015 self.wfile.write('auth=%s<p>' % auth)
1016 self.wfile.write('pairs=%s<p>' % pairs)
1017 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1018 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1019 self.wfile.write('</body></html>')
1020 return True
1021
1022 # Authentication successful.
1023 self.send_response(200)
1024 self.send_header('Content-type', 'text/html')
1025 self.end_headers()
1026 self.wfile.write('<html><head>')
1027 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1028 self.wfile.write('</head><body>')
1029 self.wfile.write('auth=%s<p>' % auth)
1030 self.wfile.write('pairs=%s<p>' % pairs)
1031 self.wfile.write('</body></html>')
1032
1033 return True
1034
1035 def SlowServerHandler(self):
1036 """Wait for the user suggested time before responding. The syntax is
1037 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001038 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001039 return False
1040 query_char = self.path.find('?')
1041 wait_sec = 1.0
1042 if query_char >= 0:
1043 try:
1044 wait_sec = int(self.path[query_char + 1:])
1045 except ValueError:
1046 pass
1047 time.sleep(wait_sec)
1048 self.send_response(200)
1049 self.send_header('Content-type', 'text/plain')
1050 self.end_headers()
1051 self.wfile.write("waited %d seconds" % wait_sec)
1052 return True
1053
1054 def ContentTypeHandler(self):
1055 """Returns a string of html with the given content type. E.g.,
1056 /contenttype?text/css returns an html file with the Content-Type
1057 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001058 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001059 return False
1060 query_char = self.path.find('?')
1061 content_type = self.path[query_char + 1:].strip()
1062 if not content_type:
1063 content_type = 'text/html'
1064 self.send_response(200)
1065 self.send_header('Content-Type', content_type)
1066 self.end_headers()
1067 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1068 return True
1069
creis@google.com2f4f6a42011-03-25 19:44:19 +00001070 def NoContentHandler(self):
1071 """Returns a 204 No Content response."""
1072 if not self._ShouldHandleRequest("/nocontent"):
1073 return False
1074 self.send_response(204)
1075 self.end_headers()
1076 return True
1077
initial.commit94958cf2008-07-26 22:42:52 +00001078 def ServerRedirectHandler(self):
1079 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001080 '/server-redirect?http://foo.bar/asdf' to redirect to
1081 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001082
1083 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001084 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001085 return False
1086
1087 query_char = self.path.find('?')
1088 if query_char < 0 or len(self.path) <= query_char + 1:
1089 self.sendRedirectHelp(test_name)
1090 return True
1091 dest = self.path[query_char + 1:]
1092
1093 self.send_response(301) # moved permanently
1094 self.send_header('Location', dest)
1095 self.send_header('Content-type', 'text/html')
1096 self.end_headers()
1097 self.wfile.write('<html><head>')
1098 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1099
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001100 return True
initial.commit94958cf2008-07-26 22:42:52 +00001101
1102 def ClientRedirectHandler(self):
1103 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001104 '/client-redirect?http://foo.bar/asdf' to redirect to
1105 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001106
1107 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001108 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001109 return False
1110
1111 query_char = self.path.find('?');
1112 if query_char < 0 or len(self.path) <= query_char + 1:
1113 self.sendRedirectHelp(test_name)
1114 return True
1115 dest = self.path[query_char + 1:]
1116
1117 self.send_response(200)
1118 self.send_header('Content-type', 'text/html')
1119 self.end_headers()
1120 self.wfile.write('<html><head>')
1121 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1122 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1123
1124 return True
1125
tony@chromium.org03266982010-03-05 03:18:42 +00001126 def MultipartHandler(self):
1127 """Send a multipart response (10 text/html pages)."""
1128 test_name = "/multipart"
1129 if not self._ShouldHandleRequest(test_name):
1130 return False
1131
1132 num_frames = 10
1133 bound = '12345'
1134 self.send_response(200)
1135 self.send_header('Content-type',
1136 'multipart/x-mixed-replace;boundary=' + bound)
1137 self.end_headers()
1138
1139 for i in xrange(num_frames):
1140 self.wfile.write('--' + bound + '\r\n')
1141 self.wfile.write('Content-type: text/html\r\n\r\n')
1142 self.wfile.write('<title>page ' + str(i) + '</title>')
1143 self.wfile.write('page ' + str(i))
1144
1145 self.wfile.write('--' + bound + '--')
1146 return True
1147
initial.commit94958cf2008-07-26 22:42:52 +00001148 def DefaultResponseHandler(self):
1149 """This is the catch-all response handler for requests that aren't handled
1150 by one of the special handlers above.
1151 Note that we specify the content-length as without it the https connection
1152 is not closed properly (and the browser keeps expecting data)."""
1153
1154 contents = "Default response given for path: " + self.path
1155 self.send_response(200)
1156 self.send_header('Content-type', 'text/html')
1157 self.send_header("Content-Length", len(contents))
1158 self.end_headers()
1159 self.wfile.write(contents)
1160 return True
1161
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001162 def RedirectConnectHandler(self):
1163 """Sends a redirect to the CONNECT request for www.redirect.com. This
1164 response is not specified by the RFC, so the browser should not follow
1165 the redirect."""
1166
1167 if (self.path.find("www.redirect.com") < 0):
1168 return False
1169
1170 dest = "http://www.destination.com/foo.js"
1171
1172 self.send_response(302) # moved temporarily
1173 self.send_header('Location', dest)
1174 self.send_header('Connection', 'close')
1175 self.end_headers()
1176 return True
1177
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001178 def ServerAuthConnectHandler(self):
1179 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1180 response doesn't make sense because the proxy server cannot request
1181 server authentication."""
1182
1183 if (self.path.find("www.server-auth.com") < 0):
1184 return False
1185
1186 challenge = 'Basic realm="WallyWorld"'
1187
1188 self.send_response(401) # unauthorized
1189 self.send_header('WWW-Authenticate', challenge)
1190 self.send_header('Connection', 'close')
1191 self.end_headers()
1192 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001193
1194 def DefaultConnectResponseHandler(self):
1195 """This is the catch-all response handler for CONNECT requests that aren't
1196 handled by one of the special handlers above. Real Web servers respond
1197 with 400 to CONNECT requests."""
1198
1199 contents = "Your client has issued a malformed or illegal request."
1200 self.send_response(400) # bad request
1201 self.send_header('Content-type', 'text/html')
1202 self.send_header("Content-Length", len(contents))
1203 self.end_headers()
1204 self.wfile.write(contents)
1205 return True
1206
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001207 def DeviceManagementHandler(self):
1208 """Delegates to the device management service used for cloud policy."""
1209 if not self._ShouldHandleRequest("/device_management"):
1210 return False
1211
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001212 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001213
1214 if not self.server._device_management_handler:
1215 import device_management
1216 policy_path = os.path.join(self.server.data_dir, 'device_management')
1217 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001218 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001219 self.server.policy_keys,
1220 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001221
1222 http_response, raw_reply = (
1223 self.server._device_management_handler.HandleRequest(self.path,
1224 self.headers,
1225 raw_request))
1226 self.send_response(http_response)
1227 self.end_headers()
1228 self.wfile.write(raw_reply)
1229 return True
1230
initial.commit94958cf2008-07-26 22:42:52 +00001231 # called by the redirect handling function when there is no parameter
1232 def sendRedirectHelp(self, redirect_name):
1233 self.send_response(200)
1234 self.send_header('Content-type', 'text/html')
1235 self.end_headers()
1236 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1237 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1238 self.wfile.write('</body></html>')
1239
akalin@chromium.org154bb132010-11-12 02:20:27 +00001240
1241class SyncPageHandler(BasePageHandler):
1242 """Handler for the main HTTP sync server."""
1243
1244 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001245 get_handlers = [self.ChromiumSyncMigrationOpHandler,
1246 self.ChromiumSyncTimeHandler]
1247 post_handlers = [self.ChromiumSyncCommandHandler,
1248 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001249 BasePageHandler.__init__(self, request, client_address,
1250 sync_http_server, [], get_handlers,
1251 post_handlers, [])
1252
1253 def ChromiumSyncTimeHandler(self):
1254 """Handle Chromium sync .../time requests.
1255
1256 The syncer sometimes checks server reachability by examining /time.
1257 """
1258 test_name = "/chromiumsync/time"
1259 if not self._ShouldHandleRequest(test_name):
1260 return False
1261
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001262 # Chrome hates it if we send a response before reading the request.
1263 if self.headers.getheader('content-length'):
1264 length = int(self.headers.getheader('content-length'))
1265 raw_request = self.rfile.read(length)
1266
akalin@chromium.org154bb132010-11-12 02:20:27 +00001267 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001268 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001269 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001270 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001271 return True
1272
1273 def ChromiumSyncCommandHandler(self):
1274 """Handle a chromiumsync command arriving via http.
1275
1276 This covers all sync protocol commands: authentication, getupdates, and
1277 commit.
1278 """
1279 test_name = "/chromiumsync/command"
1280 if not self._ShouldHandleRequest(test_name):
1281 return False
1282
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001283 length = int(self.headers.getheader('content-length'))
1284 raw_request = self.rfile.read(length)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001285
1286 http_response, raw_reply = self.server.HandleCommand(
1287 self.path, raw_request)
1288 self.send_response(http_response)
1289 self.end_headers()
1290 self.wfile.write(raw_reply)
1291 return True
1292
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001293 def ChromiumSyncMigrationOpHandler(self):
1294 """Handle a chromiumsync test-op command arriving via http.
1295 """
1296 test_name = "/chromiumsync/migrate"
1297 if not self._ShouldHandleRequest(test_name):
1298 return False
1299
1300 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1301 self.path)
1302 self.send_response(http_response)
1303 self.send_header('Content-Type', 'text/html')
1304 self.send_header('Content-Length', len(raw_reply))
1305 self.end_headers()
1306 self.wfile.write(raw_reply)
1307 return True
1308
akalin@chromium.org154bb132010-11-12 02:20:27 +00001309
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001310def MakeDataDir():
1311 if options.data_dir:
1312 if not os.path.isdir(options.data_dir):
1313 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1314 return None
1315 my_data_dir = options.data_dir
1316 else:
1317 # Create the default path to our data dir, relative to the exe dir.
1318 my_data_dir = os.path.dirname(sys.argv[0])
1319 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001320 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001321
1322 #TODO(ibrar): Must use Find* funtion defined in google\tools
1323 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1324
1325 return my_data_dir
1326
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001327class FileMultiplexer:
1328 def __init__(self, fd1, fd2) :
1329 self.__fd1 = fd1
1330 self.__fd2 = fd2
1331
1332 def __del__(self) :
1333 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1334 self.__fd1.close()
1335 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1336 self.__fd2.close()
1337
1338 def write(self, text) :
1339 self.__fd1.write(text)
1340 self.__fd2.write(text)
1341
1342 def flush(self) :
1343 self.__fd1.flush()
1344 self.__fd2.flush()
1345
initial.commit94958cf2008-07-26 22:42:52 +00001346def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001347 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001348 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001349 if options.log_to_console:
1350 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1351 else:
1352 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001353
1354 port = options.port
1355
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001356 server_data = {}
1357
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001358 if options.server_type == SERVER_HTTP:
1359 if options.cert:
1360 # let's make sure the cert file exists.
1361 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001362 print 'specified server cert file not found: ' + options.cert + \
1363 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001364 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001365 for ca_cert in options.ssl_client_ca:
1366 if not os.path.isfile(ca_cert):
1367 print 'specified trusted client CA file not found: ' + ca_cert + \
1368 ' exiting...'
1369 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001370 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001371 options.ssl_client_auth, options.ssl_client_ca,
1372 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001373 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001374 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001375 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001376 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001377
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001378 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001379 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001380 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001381 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001382 server.policy_keys = options.policy_keys
1383 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001384 elif options.server_type == SERVER_SYNC:
1385 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1386 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001387 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1388 server_data['port'] = server.server_port
1389 server_data['xmpp_port'] = server.xmpp_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001390 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001391 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001392 my_data_dir = MakeDataDir()
1393
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001394 # Instantiate a dummy authorizer for managing 'virtual' users
1395 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1396
1397 # Define a new user having full r/w permissions and a read-only
1398 # anonymous user
1399 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1400
1401 authorizer.add_anonymous(my_data_dir)
1402
1403 # Instantiate FTP handler class
1404 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1405 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001406
1407 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001408 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1409 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001410
1411 # Instantiate FTP server class and listen to 127.0.0.1:port
1412 address = ('127.0.0.1', port)
1413 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001414 server_data['port'] = server.socket.getsockname()[1]
1415 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001416
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001417 # Notify the parent that we've started. (BaseServer subclasses
1418 # bind their sockets on construction.)
1419 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001420 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001421 server_data_len = len(server_data_json)
1422 print 'sending server_data: %s (%d bytes)' % (
1423 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001424 if sys.platform == 'win32':
1425 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1426 else:
1427 fd = options.startup_pipe
1428 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001429 # First write the data length as an unsigned 4-byte value. This
1430 # is _not_ using network byte ordering since the other end of the
1431 # pipe is on the same machine.
1432 startup_pipe.write(struct.pack('=L', server_data_len))
1433 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001434 startup_pipe.close()
1435
initial.commit94958cf2008-07-26 22:42:52 +00001436 try:
1437 server.serve_forever()
1438 except KeyboardInterrupt:
1439 print 'shutting down server'
1440 server.stop = True
1441
1442if __name__ == '__main__':
1443 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001444 option_parser.add_option("-f", '--ftp', action='store_const',
1445 const=SERVER_FTP, default=SERVER_HTTP,
1446 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001447 help='start up an FTP server.')
1448 option_parser.add_option('', '--sync', action='store_const',
1449 const=SERVER_SYNC, default=SERVER_HTTP,
1450 dest='server_type',
1451 help='start up a sync server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001452 option_parser.add_option('', '--log-to-console', action='store_const',
1453 const=True, default=False,
1454 dest='log_to_console',
1455 help='Enables or disables sys.stdout logging to '
1456 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001457 option_parser.add_option('', '--port', default='0', type='int',
1458 help='Port used by the server. If unspecified, the '
1459 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001460 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001461 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001462 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001463 help='Specify that https should be used, specify '
1464 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001465 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001466 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1467 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001468 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1469 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001470 'should include the CA named in the subject of '
1471 'the DER-encoded certificate contained in the '
1472 'specified file. This option may appear multiple '
1473 'times, indicating multiple CA names should be '
1474 'sent in the request.')
1475 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1476 help='Specify the bulk encryption algorithm(s)'
1477 'that will be accepted by the SSL server. Valid '
1478 'values are "aes256", "aes128", "3des", "rc4". If '
1479 'omitted, all algorithms will be used. This '
1480 'option may appear multiple times, indicating '
1481 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001482 option_parser.add_option('', '--file-root-url', default='/files/',
1483 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001484 option_parser.add_option('', '--startup-pipe', type='int',
1485 dest='startup_pipe',
1486 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001487 option_parser.add_option('', '--policy-key', action='append',
1488 dest='policy_keys',
1489 help='Specify a path to a PEM-encoded private key '
1490 'to use for policy signing. May be specified '
1491 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001492 'the server. If ther server has multiple keys, it '
1493 'will rotate through them in at each request a '
1494 'round-robin fashion. The server will generate a '
1495 'random key if none is specified on the command '
1496 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001497 option_parser.add_option('', '--policy-user', default='user@example.com',
1498 dest='policy_user',
1499 help='Specify the user name the server should '
1500 'report back to the client as the user owning the '
1501 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001502 options, args = option_parser.parse_args()
1503
1504 sys.exit(main(options, args))