blob: f44fabc543191e806e88df73d93ebacaebfb40e3 [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00002# Copyright (c) 2006-2010 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.org219b2062009-10-23 16:09:41 +0000281 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000282 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000283 self.FileHandler,
284 self.RealFileWithCommonHeaderHandler,
285 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000286 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000287 self.AuthBasicHandler,
288 self.AuthDigestHandler,
289 self.SlowServerHandler,
290 self.ContentTypeHandler,
291 self.ServerRedirectHandler,
292 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000293 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000294 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000295 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000296 self.EchoTitleHandler,
297 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000298 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000299 self.DeviceManagementHandler] + get_handlers
300 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000301 self.EchoTitleHandler,
302 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000304
maruel@google.come250a9b2009-03-10 17:39:46 +0000305 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000306 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000307 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000308 'gif': 'image/gif',
309 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000310 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000311 'pdf' : 'application/pdf',
312 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000313 }
initial.commit94958cf2008-07-26 22:42:52 +0000314 self._default_mime_type = 'text/html'
315
akalin@chromium.org154bb132010-11-12 02:20:27 +0000316 BasePageHandler.__init__(self, request, client_address, socket_server,
317 connect_handlers, get_handlers, post_handlers,
318 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000319
initial.commit94958cf2008-07-26 22:42:52 +0000320 def GetMIMETypeFromName(self, file_name):
321 """Returns the mime type for the specified file_name. So far it only looks
322 at the file extension."""
323
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000324 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000325 if len(extension) == 0:
326 # no extension.
327 return self._default_mime_type
328
ericroman@google.comc17ca532009-05-07 03:51:05 +0000329 # extension starts with a dot, so we need to remove it
330 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000331
initial.commit94958cf2008-07-26 22:42:52 +0000332 def NoCacheMaxAgeTimeHandler(self):
333 """This request handler yields a page with the title set to the current
334 system time, and no caching requested."""
335
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000336 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000337 return False
338
339 self.send_response(200)
340 self.send_header('Cache-Control', 'max-age=0')
341 self.send_header('Content-type', 'text/html')
342 self.end_headers()
343
maruel@google.come250a9b2009-03-10 17:39:46 +0000344 self.wfile.write('<html><head><title>%s</title></head></html>' %
345 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000346
347 return True
348
349 def NoCacheTimeHandler(self):
350 """This request handler yields a page with the title set to the current
351 system time, and no caching requested."""
352
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000353 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000354 return False
355
356 self.send_response(200)
357 self.send_header('Cache-Control', 'no-cache')
358 self.send_header('Content-type', 'text/html')
359 self.end_headers()
360
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 self.wfile.write('<html><head><title>%s</title></head></html>' %
362 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000363
364 return True
365
366 def CacheTimeHandler(self):
367 """This request handler yields a page with the title set to the current
368 system time, and allows caching for one minute."""
369
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000370 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000371 return False
372
373 self.send_response(200)
374 self.send_header('Cache-Control', 'max-age=60')
375 self.send_header('Content-type', 'text/html')
376 self.end_headers()
377
maruel@google.come250a9b2009-03-10 17:39:46 +0000378 self.wfile.write('<html><head><title>%s</title></head></html>' %
379 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000380
381 return True
382
383 def CacheExpiresHandler(self):
384 """This request handler yields a page with the title set to the current
385 system time, and set the page to expire on 1 Jan 2099."""
386
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000387 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000388 return False
389
390 self.send_response(200)
391 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
392 self.send_header('Content-type', 'text/html')
393 self.end_headers()
394
maruel@google.come250a9b2009-03-10 17:39:46 +0000395 self.wfile.write('<html><head><title>%s</title></head></html>' %
396 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000397
398 return True
399
400 def CacheProxyRevalidateHandler(self):
401 """This request handler yields a page with the title set to the current
402 system time, and allows caching for 60 seconds"""
403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
408 self.send_header('Content-type', 'text/html')
409 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417 def CachePrivateHandler(self):
418 """This request handler yields a page with the title set to the current
419 system time, and allows caching for 5 seconds."""
420
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000421 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000422 return False
423
424 self.send_response(200)
425 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000426 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000427 self.end_headers()
428
maruel@google.come250a9b2009-03-10 17:39:46 +0000429 self.wfile.write('<html><head><title>%s</title></head></html>' %
430 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000431
432 return True
433
434 def CachePublicHandler(self):
435 """This request handler yields a page with the title set to the current
436 system time, and allows caching for 5 seconds."""
437
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000438 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000439 return False
440
441 self.send_response(200)
442 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000443 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000444 self.end_headers()
445
maruel@google.come250a9b2009-03-10 17:39:46 +0000446 self.wfile.write('<html><head><title>%s</title></head></html>' %
447 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000448
449 return True
450
451 def CacheSMaxAgeHandler(self):
452 """This request handler yields a page with the title set to the current
453 system time, and does not allow for caching."""
454
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000455 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000456 return False
457
458 self.send_response(200)
459 self.send_header('Content-type', 'text/html')
460 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
461 self.end_headers()
462
maruel@google.come250a9b2009-03-10 17:39:46 +0000463 self.wfile.write('<html><head><title>%s</title></head></html>' %
464 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000465
466 return True
467
468 def CacheMustRevalidateHandler(self):
469 """This request handler yields a page with the title set to the current
470 system time, and does not allow caching."""
471
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000472 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000473 return False
474
475 self.send_response(200)
476 self.send_header('Content-type', 'text/html')
477 self.send_header('Cache-Control', 'must-revalidate')
478 self.end_headers()
479
maruel@google.come250a9b2009-03-10 17:39:46 +0000480 self.wfile.write('<html><head><title>%s</title></head></html>' %
481 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000482
483 return True
484
485 def CacheMustRevalidateMaxAgeHandler(self):
486 """This request handler yields a page with the title set to the current
487 system time, and does not allow caching event though max-age of 60
488 seconds is specified."""
489
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000490 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000491 return False
492
493 self.send_response(200)
494 self.send_header('Content-type', 'text/html')
495 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
496 self.end_headers()
497
maruel@google.come250a9b2009-03-10 17:39:46 +0000498 self.wfile.write('<html><head><title>%s</title></head></html>' %
499 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000500
501 return True
502
initial.commit94958cf2008-07-26 22:42:52 +0000503 def CacheNoStoreHandler(self):
504 """This request handler yields a page with the title set to the current
505 system time, and does not allow the page to be stored."""
506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
511 self.send_header('Content-type', 'text/html')
512 self.send_header('Cache-Control', 'no-store')
513 self.end_headers()
514
maruel@google.come250a9b2009-03-10 17:39:46 +0000515 self.wfile.write('<html><head><title>%s</title></head></html>' %
516 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000517
518 return True
519
520 def CacheNoStoreMaxAgeHandler(self):
521 """This request handler yields a page with the title set to the current
522 system time, and does not allow the page to be stored even though max-age
523 of 60 seconds is specified."""
524
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000525 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000526 return False
527
528 self.send_response(200)
529 self.send_header('Content-type', 'text/html')
530 self.send_header('Cache-Control', 'max-age=60, no-store')
531 self.end_headers()
532
maruel@google.come250a9b2009-03-10 17:39:46 +0000533 self.wfile.write('<html><head><title>%s</title></head></html>' %
534 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000535
536 return True
537
538
539 def CacheNoTransformHandler(self):
540 """This request handler yields a page with the title set to the current
541 system time, and does not allow the content to transformed during
542 user-agent caching"""
543
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000544 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000545 return False
546
547 self.send_response(200)
548 self.send_header('Content-type', 'text/html')
549 self.send_header('Cache-Control', 'no-transform')
550 self.end_headers()
551
maruel@google.come250a9b2009-03-10 17:39:46 +0000552 self.wfile.write('<html><head><title>%s</title></head></html>' %
553 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000554
555 return True
556
557 def EchoHeader(self):
558 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000559 """The only difference between this function and the EchoHeaderOverride"""
560 """function is in the parameter being passed to the helper function"""
561 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000562
ananta@chromium.org219b2062009-10-23 16:09:41 +0000563 def EchoHeaderOverride(self):
564 """This handler echoes back the value of a specific request header."""
565 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
566 """IE to issue HTTP requests using the host network stack."""
567 """The Accept and Charset tests which expect the server to echo back"""
568 """the corresponding headers fail here as IE returns cached responses"""
569 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
570 """treats this request as a new request and does not cache it."""
571 return self.EchoHeaderHelper("/echoheaderoverride")
572
573 def EchoHeaderHelper(self, echo_header):
574 """This function echoes back the value of the request header passed in."""
575 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000576 return False
577
578 query_char = self.path.find('?')
579 if query_char != -1:
580 header_name = self.path[query_char+1:]
581
582 self.send_response(200)
583 self.send_header('Content-type', 'text/plain')
584 self.send_header('Cache-control', 'max-age=60000')
585 # insert a vary header to properly indicate that the cachability of this
586 # request is subject to value of the request header being echoed.
587 if len(header_name) > 0:
588 self.send_header('Vary', header_name)
589 self.end_headers()
590
591 if len(header_name) > 0:
592 self.wfile.write(self.headers.getheader(header_name))
593
594 return True
595
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000596 def ReadRequestBody(self):
597 """This function reads the body of the current HTTP request, handling
598 both plain and chunked transfer encoded requests."""
599
600 if self.headers.getheader('transfer-encoding') != 'chunked':
601 length = int(self.headers.getheader('content-length'))
602 return self.rfile.read(length)
603
604 # Read the request body as chunks.
605 body = ""
606 while True:
607 line = self.rfile.readline()
608 length = int(line, 16)
609 if length == 0:
610 self.rfile.readline()
611 break
612 body += self.rfile.read(length)
613 self.rfile.read(2)
614 return body
615
initial.commit94958cf2008-07-26 22:42:52 +0000616 def EchoHandler(self):
617 """This handler just echoes back the payload of the request, for testing
618 form submission."""
619
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000620 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000621 return False
622
623 self.send_response(200)
624 self.send_header('Content-type', 'text/html')
625 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000626 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000627 return True
628
629 def EchoTitleHandler(self):
630 """This handler is like Echo, but sets the page title to the request."""
631
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000632 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000633 return False
634
635 self.send_response(200)
636 self.send_header('Content-type', 'text/html')
637 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000638 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000639 self.wfile.write('<html><head><title>')
640 self.wfile.write(request)
641 self.wfile.write('</title></head></html>')
642 return True
643
644 def EchoAllHandler(self):
645 """This handler yields a (more) human-readable page listing information
646 about the request header & contents."""
647
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000648 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000649 return False
650
651 self.send_response(200)
652 self.send_header('Content-type', 'text/html')
653 self.end_headers()
654 self.wfile.write('<html><head><style>'
655 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
656 '</style></head><body>'
657 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000658 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000659 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000660
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000661 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000662 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000663 params = cgi.parse_qs(qs, keep_blank_values=1)
664
665 for param in params:
666 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000667
668 self.wfile.write('</pre>')
669
670 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
671
672 self.wfile.write('</body></html>')
673 return True
674
675 def DownloadHandler(self):
676 """This handler sends a downloadable file with or without reporting
677 the size (6K)."""
678
679 if self.path.startswith("/download-unknown-size"):
680 send_length = False
681 elif self.path.startswith("/download-known-size"):
682 send_length = True
683 else:
684 return False
685
686 #
687 # The test which uses this functionality is attempting to send
688 # small chunks of data to the client. Use a fairly large buffer
689 # so that we'll fill chrome's IO buffer enough to force it to
690 # actually write the data.
691 # See also the comments in the client-side of this test in
692 # download_uitest.cc
693 #
694 size_chunk1 = 35*1024
695 size_chunk2 = 10*1024
696
697 self.send_response(200)
698 self.send_header('Content-type', 'application/octet-stream')
699 self.send_header('Cache-Control', 'max-age=0')
700 if send_length:
701 self.send_header('Content-Length', size_chunk1 + size_chunk2)
702 self.end_headers()
703
704 # First chunk of data:
705 self.wfile.write("*" * size_chunk1)
706 self.wfile.flush()
707
708 # handle requests until one of them clears this flag.
709 self.server.waitForDownload = True
710 while self.server.waitForDownload:
711 self.server.handle_request()
712
713 # Second chunk of data:
714 self.wfile.write("*" * size_chunk2)
715 return True
716
717 def DownloadFinishHandler(self):
718 """This handler just tells the server to finish the current download."""
719
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000720 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000721 return False
722
723 self.server.waitForDownload = False
724 self.send_response(200)
725 self.send_header('Content-type', 'text/html')
726 self.send_header('Cache-Control', 'max-age=0')
727 self.end_headers()
728 return True
729
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000730 def _ReplaceFileData(self, data, query_parameters):
731 """Replaces matching substrings in a file.
732
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000733 If the 'replace_text' URL query parameter is present, it is expected to be
734 of the form old_text:new_text, which indicates that any old_text strings in
735 the file are replaced with new_text. Multiple 'replace_text' parameters may
736 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000737
738 If the parameters are not present, |data| is returned.
739 """
740 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000741 replace_text_values = query_dict.get('replace_text', [])
742 for replace_text_value in replace_text_values:
743 replace_text_args = replace_text_value.split(':')
744 if len(replace_text_args) != 2:
745 raise ValueError(
746 'replace_text must be of form old_text:new_text. Actual value: %s' %
747 replace_text_value)
748 old_text_b64, new_text_b64 = replace_text_args
749 old_text = base64.urlsafe_b64decode(old_text_b64)
750 new_text = base64.urlsafe_b64decode(new_text_b64)
751 data = data.replace(old_text, new_text)
752 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000753
initial.commit94958cf2008-07-26 22:42:52 +0000754 def FileHandler(self):
755 """This handler sends the contents of the requested file. Wow, it's like
756 a real webserver!"""
757
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000758 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000759 if not self.path.startswith(prefix):
760 return False
761
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000762 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000763 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000764 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000765
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000766 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
767 sub_path = url_path[len(prefix):]
768 entries = sub_path.split('/')
769 file_path = os.path.join(self.server.data_dir, *entries)
770 if os.path.isdir(file_path):
771 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000772
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000773 if not os.path.isfile(file_path):
774 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000775 self.send_error(404)
776 return True
777
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000778 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000779 data = f.read()
780 f.close()
781
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000782 data = self._ReplaceFileData(data, query)
783
initial.commit94958cf2008-07-26 22:42:52 +0000784 # If file.mock-http-headers exists, it contains the headers we
785 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000786 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000787 if os.path.isfile(headers_path):
788 f = open(headers_path, "r")
789
790 # "HTTP/1.1 200 OK"
791 response = f.readline()
792 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
793 self.send_response(int(status_code))
794
795 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000796 header_values = re.findall('(\S+):\s*(.*)', line)
797 if len(header_values) > 0:
798 # "name: value"
799 name, value = header_values[0]
800 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000801 f.close()
802 else:
803 # Could be more generic once we support mime-type sniffing, but for
804 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000805
806 range = self.headers.get('Range')
807 if range and range.startswith('bytes='):
808 # Note this doesn't handle all valid byte range values (i.e. open ended
809 # ones), just enough for what we needed so far.
810 range = range[6:].split('-')
811 start = int(range[0])
812 end = int(range[1])
813
814 self.send_response(206)
815 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
816 str(len(data))
817 self.send_header('Content-Range', content_range)
818 data = data[start: end + 1]
819 else:
820 self.send_response(200)
821
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000822 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000823 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000824 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000825 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000826 self.end_headers()
827
828 self.wfile.write(data)
829
830 return True
831
832 def RealFileWithCommonHeaderHandler(self):
833 """This handler sends the contents of the requested file without the pseudo
834 http head!"""
835
836 prefix='/realfiles/'
837 if not self.path.startswith(prefix):
838 return False
839
840 file = self.path[len(prefix):]
841 path = os.path.join(self.server.data_dir, file)
842
843 try:
844 f = open(path, "rb")
845 data = f.read()
846 f.close()
847
848 # just simply set the MIME as octal stream
849 self.send_response(200)
850 self.send_header('Content-type', 'application/octet-stream')
851 self.end_headers()
852
853 self.wfile.write(data)
854 except:
855 self.send_error(404)
856
857 return True
858
859 def RealBZ2FileWithCommonHeaderHandler(self):
860 """This handler sends the bzip2 contents of the requested file with
861 corresponding Content-Encoding field in http head!"""
862
863 prefix='/realbz2files/'
864 if not self.path.startswith(prefix):
865 return False
866
867 parts = self.path.split('?')
868 file = parts[0][len(prefix):]
869 path = os.path.join(self.server.data_dir, file) + '.bz2'
870
871 if len(parts) > 1:
872 options = parts[1]
873 else:
874 options = ''
875
876 try:
877 self.send_response(200)
878 accept_encoding = self.headers.get("Accept-Encoding")
879 if accept_encoding.find("bzip2") != -1:
880 f = open(path, "rb")
881 data = f.read()
882 f.close()
883 self.send_header('Content-Encoding', 'bzip2')
884 self.send_header('Content-type', 'application/x-bzip2')
885 self.end_headers()
886 if options == 'incremental-header':
887 self.wfile.write(data[:1])
888 self.wfile.flush()
889 time.sleep(1.0)
890 self.wfile.write(data[1:])
891 else:
892 self.wfile.write(data)
893 else:
894 """client do not support bzip2 format, send pseudo content
895 """
896 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
897 self.end_headers()
898 self.wfile.write("you do not support bzip2 encoding")
899 except:
900 self.send_error(404)
901
902 return True
903
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000904 def SetCookieHandler(self):
905 """This handler just sets a cookie, for testing cookie handling."""
906
907 if not self._ShouldHandleRequest("/set-cookie"):
908 return False
909
910 query_char = self.path.find('?')
911 if query_char != -1:
912 cookie_values = self.path[query_char + 1:].split('&')
913 else:
914 cookie_values = ("",)
915 self.send_response(200)
916 self.send_header('Content-type', 'text/html')
917 for cookie_value in cookie_values:
918 self.send_header('Set-Cookie', '%s' % cookie_value)
919 self.end_headers()
920 for cookie_value in cookie_values:
921 self.wfile.write('%s' % cookie_value)
922 return True
923
initial.commit94958cf2008-07-26 22:42:52 +0000924 def AuthBasicHandler(self):
925 """This handler tests 'Basic' authentication. It just sends a page with
926 title 'user/pass' if you succeed."""
927
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000928 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000929 return False
930
931 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000932 expected_password = 'secret'
933 realm = 'testrealm'
934 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000935
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000936 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
937 query_params = cgi.parse_qs(query, True)
938 if 'set-cookie-if-challenged' in query_params:
939 set_cookie_if_challenged = True
940 if 'password' in query_params:
941 expected_password = query_params['password'][0]
942 if 'realm' in query_params:
943 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000944
initial.commit94958cf2008-07-26 22:42:52 +0000945 auth = self.headers.getheader('authorization')
946 try:
947 if not auth:
948 raise Exception('no auth')
949 b64str = re.findall(r'Basic (\S+)', auth)[0]
950 userpass = base64.b64decode(b64str)
951 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000952 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000953 raise Exception('wrong password')
954 except Exception, e:
955 # Authentication failed.
956 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000957 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000958 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000959 if set_cookie_if_challenged:
960 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000961 self.end_headers()
962 self.wfile.write('<html><head>')
963 self.wfile.write('<title>Denied: %s</title>' % e)
964 self.wfile.write('</head><body>')
965 self.wfile.write('auth=%s<p>' % auth)
966 self.wfile.write('b64str=%s<p>' % b64str)
967 self.wfile.write('username: %s<p>' % username)
968 self.wfile.write('userpass: %s<p>' % userpass)
969 self.wfile.write('password: %s<p>' % password)
970 self.wfile.write('You sent:<br>%s<p>' % self.headers)
971 self.wfile.write('</body></html>')
972 return True
973
974 # Authentication successful. (Return a cachable response to allow for
975 # testing cached pages that require authentication.)
976 if_none_match = self.headers.getheader('if-none-match')
977 if if_none_match == "abc":
978 self.send_response(304)
979 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000980 elif url_path.endswith(".gif"):
981 # Using chrome/test/data/google/logo.gif as the test image
982 test_image_path = ['google', 'logo.gif']
983 gif_path = os.path.join(self.server.data_dir, *test_image_path)
984 if not os.path.isfile(gif_path):
985 self.send_error(404)
986 return True
987
988 f = open(gif_path, "rb")
989 data = f.read()
990 f.close()
991
992 self.send_response(200)
993 self.send_header('Content-type', 'image/gif')
994 self.send_header('Cache-control', 'max-age=60000')
995 self.send_header('Etag', 'abc')
996 self.end_headers()
997 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000998 else:
999 self.send_response(200)
1000 self.send_header('Content-type', 'text/html')
1001 self.send_header('Cache-control', 'max-age=60000')
1002 self.send_header('Etag', 'abc')
1003 self.end_headers()
1004 self.wfile.write('<html><head>')
1005 self.wfile.write('<title>%s/%s</title>' % (username, password))
1006 self.wfile.write('</head><body>')
1007 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001008 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001009 self.wfile.write('</body></html>')
1010
1011 return True
1012
tonyg@chromium.org75054202010-03-31 22:06:10 +00001013 def GetNonce(self, force_reset=False):
1014 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001015
tonyg@chromium.org75054202010-03-31 22:06:10 +00001016 This is a fake implementation. A real implementation would only use a given
1017 nonce a single time (hence the name n-once). However, for the purposes of
1018 unittesting, we don't care about the security of the nonce.
1019
1020 Args:
1021 force_reset: Iff set, the nonce will be changed. Useful for testing the
1022 "stale" response.
1023 """
1024 if force_reset or not self.server.nonce_time:
1025 self.server.nonce_time = time.time()
1026 return _new_md5('privatekey%s%d' %
1027 (self.path, self.server.nonce_time)).hexdigest()
1028
1029 def AuthDigestHandler(self):
1030 """This handler tests 'Digest' authentication.
1031
1032 It just sends a page with title 'user/pass' if you succeed.
1033
1034 A stale response is sent iff "stale" is present in the request path.
1035 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001036 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001037 return False
1038
tonyg@chromium.org75054202010-03-31 22:06:10 +00001039 stale = 'stale' in self.path
1040 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001041 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001042 password = 'secret'
1043 realm = 'testrealm'
1044
1045 auth = self.headers.getheader('authorization')
1046 pairs = {}
1047 try:
1048 if not auth:
1049 raise Exception('no auth')
1050 if not auth.startswith('Digest'):
1051 raise Exception('not digest')
1052 # Pull out all the name="value" pairs as a dictionary.
1053 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1054
1055 # Make sure it's all valid.
1056 if pairs['nonce'] != nonce:
1057 raise Exception('wrong nonce')
1058 if pairs['opaque'] != opaque:
1059 raise Exception('wrong opaque')
1060
1061 # Check the 'response' value and make sure it matches our magic hash.
1062 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001063 hash_a1 = _new_md5(
1064 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001065 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001066 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001067 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001068 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1069 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001070 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001071
1072 if pairs['response'] != response:
1073 raise Exception('wrong password')
1074 except Exception, e:
1075 # Authentication failed.
1076 self.send_response(401)
1077 hdr = ('Digest '
1078 'realm="%s", '
1079 'domain="/", '
1080 'qop="auth", '
1081 'algorithm=MD5, '
1082 'nonce="%s", '
1083 'opaque="%s"') % (realm, nonce, opaque)
1084 if stale:
1085 hdr += ', stale="TRUE"'
1086 self.send_header('WWW-Authenticate', hdr)
1087 self.send_header('Content-type', 'text/html')
1088 self.end_headers()
1089 self.wfile.write('<html><head>')
1090 self.wfile.write('<title>Denied: %s</title>' % e)
1091 self.wfile.write('</head><body>')
1092 self.wfile.write('auth=%s<p>' % auth)
1093 self.wfile.write('pairs=%s<p>' % pairs)
1094 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1095 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1096 self.wfile.write('</body></html>')
1097 return True
1098
1099 # Authentication successful.
1100 self.send_response(200)
1101 self.send_header('Content-type', 'text/html')
1102 self.end_headers()
1103 self.wfile.write('<html><head>')
1104 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1105 self.wfile.write('</head><body>')
1106 self.wfile.write('auth=%s<p>' % auth)
1107 self.wfile.write('pairs=%s<p>' % pairs)
1108 self.wfile.write('</body></html>')
1109
1110 return True
1111
1112 def SlowServerHandler(self):
1113 """Wait for the user suggested time before responding. The syntax is
1114 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001115 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001116 return False
1117 query_char = self.path.find('?')
1118 wait_sec = 1.0
1119 if query_char >= 0:
1120 try:
1121 wait_sec = int(self.path[query_char + 1:])
1122 except ValueError:
1123 pass
1124 time.sleep(wait_sec)
1125 self.send_response(200)
1126 self.send_header('Content-type', 'text/plain')
1127 self.end_headers()
1128 self.wfile.write("waited %d seconds" % wait_sec)
1129 return True
1130
1131 def ContentTypeHandler(self):
1132 """Returns a string of html with the given content type. E.g.,
1133 /contenttype?text/css returns an html file with the Content-Type
1134 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001135 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001136 return False
1137 query_char = self.path.find('?')
1138 content_type = self.path[query_char + 1:].strip()
1139 if not content_type:
1140 content_type = 'text/html'
1141 self.send_response(200)
1142 self.send_header('Content-Type', content_type)
1143 self.end_headers()
1144 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1145 return True
1146
1147 def ServerRedirectHandler(self):
1148 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001149 '/server-redirect?http://foo.bar/asdf' to redirect to
1150 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001151
1152 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001153 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001154 return False
1155
1156 query_char = self.path.find('?')
1157 if query_char < 0 or len(self.path) <= query_char + 1:
1158 self.sendRedirectHelp(test_name)
1159 return True
1160 dest = self.path[query_char + 1:]
1161
1162 self.send_response(301) # moved permanently
1163 self.send_header('Location', dest)
1164 self.send_header('Content-type', 'text/html')
1165 self.end_headers()
1166 self.wfile.write('<html><head>')
1167 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1168
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001169 return True
initial.commit94958cf2008-07-26 22:42:52 +00001170
1171 def ClientRedirectHandler(self):
1172 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001173 '/client-redirect?http://foo.bar/asdf' to redirect to
1174 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001175
1176 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001177 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001178 return False
1179
1180 query_char = self.path.find('?');
1181 if query_char < 0 or len(self.path) <= query_char + 1:
1182 self.sendRedirectHelp(test_name)
1183 return True
1184 dest = self.path[query_char + 1:]
1185
1186 self.send_response(200)
1187 self.send_header('Content-type', 'text/html')
1188 self.end_headers()
1189 self.wfile.write('<html><head>')
1190 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1191 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1192
1193 return True
1194
tony@chromium.org03266982010-03-05 03:18:42 +00001195 def MultipartHandler(self):
1196 """Send a multipart response (10 text/html pages)."""
1197 test_name = "/multipart"
1198 if not self._ShouldHandleRequest(test_name):
1199 return False
1200
1201 num_frames = 10
1202 bound = '12345'
1203 self.send_response(200)
1204 self.send_header('Content-type',
1205 'multipart/x-mixed-replace;boundary=' + bound)
1206 self.end_headers()
1207
1208 for i in xrange(num_frames):
1209 self.wfile.write('--' + bound + '\r\n')
1210 self.wfile.write('Content-type: text/html\r\n\r\n')
1211 self.wfile.write('<title>page ' + str(i) + '</title>')
1212 self.wfile.write('page ' + str(i))
1213
1214 self.wfile.write('--' + bound + '--')
1215 return True
1216
initial.commit94958cf2008-07-26 22:42:52 +00001217 def DefaultResponseHandler(self):
1218 """This is the catch-all response handler for requests that aren't handled
1219 by one of the special handlers above.
1220 Note that we specify the content-length as without it the https connection
1221 is not closed properly (and the browser keeps expecting data)."""
1222
1223 contents = "Default response given for path: " + self.path
1224 self.send_response(200)
1225 self.send_header('Content-type', 'text/html')
1226 self.send_header("Content-Length", len(contents))
1227 self.end_headers()
1228 self.wfile.write(contents)
1229 return True
1230
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001231 def RedirectConnectHandler(self):
1232 """Sends a redirect to the CONNECT request for www.redirect.com. This
1233 response is not specified by the RFC, so the browser should not follow
1234 the redirect."""
1235
1236 if (self.path.find("www.redirect.com") < 0):
1237 return False
1238
1239 dest = "http://www.destination.com/foo.js"
1240
1241 self.send_response(302) # moved temporarily
1242 self.send_header('Location', dest)
1243 self.send_header('Connection', 'close')
1244 self.end_headers()
1245 return True
1246
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001247 def ServerAuthConnectHandler(self):
1248 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1249 response doesn't make sense because the proxy server cannot request
1250 server authentication."""
1251
1252 if (self.path.find("www.server-auth.com") < 0):
1253 return False
1254
1255 challenge = 'Basic realm="WallyWorld"'
1256
1257 self.send_response(401) # unauthorized
1258 self.send_header('WWW-Authenticate', challenge)
1259 self.send_header('Connection', 'close')
1260 self.end_headers()
1261 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001262
1263 def DefaultConnectResponseHandler(self):
1264 """This is the catch-all response handler for CONNECT requests that aren't
1265 handled by one of the special handlers above. Real Web servers respond
1266 with 400 to CONNECT requests."""
1267
1268 contents = "Your client has issued a malformed or illegal request."
1269 self.send_response(400) # bad request
1270 self.send_header('Content-type', 'text/html')
1271 self.send_header("Content-Length", len(contents))
1272 self.end_headers()
1273 self.wfile.write(contents)
1274 return True
1275
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001276 def DeviceManagementHandler(self):
1277 """Delegates to the device management service used for cloud policy."""
1278 if not self._ShouldHandleRequest("/device_management"):
1279 return False
1280
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001281 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001282
1283 if not self.server._device_management_handler:
1284 import device_management
1285 policy_path = os.path.join(self.server.data_dir, 'device_management')
1286 self.server._device_management_handler = (
1287 device_management.TestServer(policy_path))
1288
1289 http_response, raw_reply = (
1290 self.server._device_management_handler.HandleRequest(self.path,
1291 self.headers,
1292 raw_request))
1293 self.send_response(http_response)
1294 self.end_headers()
1295 self.wfile.write(raw_reply)
1296 return True
1297
initial.commit94958cf2008-07-26 22:42:52 +00001298 # called by the redirect handling function when there is no parameter
1299 def sendRedirectHelp(self, redirect_name):
1300 self.send_response(200)
1301 self.send_header('Content-type', 'text/html')
1302 self.end_headers()
1303 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1304 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1305 self.wfile.write('</body></html>')
1306
akalin@chromium.org154bb132010-11-12 02:20:27 +00001307
1308class SyncPageHandler(BasePageHandler):
1309 """Handler for the main HTTP sync server."""
1310
1311 def __init__(self, request, client_address, sync_http_server):
1312 get_handlers = [self.ChromiumSyncTimeHandler]
1313 post_handlers = [self.ChromiumSyncCommandHandler]
1314 BasePageHandler.__init__(self, request, client_address,
1315 sync_http_server, [], get_handlers,
1316 post_handlers, [])
1317
1318 def ChromiumSyncTimeHandler(self):
1319 """Handle Chromium sync .../time requests.
1320
1321 The syncer sometimes checks server reachability by examining /time.
1322 """
1323 test_name = "/chromiumsync/time"
1324 if not self._ShouldHandleRequest(test_name):
1325 return False
1326
1327 self.send_response(200)
1328 self.send_header('Content-type', 'text/html')
1329 self.end_headers()
1330 return True
1331
1332 def ChromiumSyncCommandHandler(self):
1333 """Handle a chromiumsync command arriving via http.
1334
1335 This covers all sync protocol commands: authentication, getupdates, and
1336 commit.
1337 """
1338 test_name = "/chromiumsync/command"
1339 if not self._ShouldHandleRequest(test_name):
1340 return False
1341
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001342 raw_request = self.ReadRequestBody()
akalin@chromium.org154bb132010-11-12 02:20:27 +00001343
1344 http_response, raw_reply = self.server.HandleCommand(
1345 self.path, raw_request)
1346 self.send_response(http_response)
1347 self.end_headers()
1348 self.wfile.write(raw_reply)
1349 return True
1350
1351
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001352def MakeDataDir():
1353 if options.data_dir:
1354 if not os.path.isdir(options.data_dir):
1355 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1356 return None
1357 my_data_dir = options.data_dir
1358 else:
1359 # Create the default path to our data dir, relative to the exe dir.
1360 my_data_dir = os.path.dirname(sys.argv[0])
1361 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001362 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001363
1364 #TODO(ibrar): Must use Find* funtion defined in google\tools
1365 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1366
1367 return my_data_dir
1368
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001369class FileMultiplexer:
1370 def __init__(self, fd1, fd2) :
1371 self.__fd1 = fd1
1372 self.__fd2 = fd2
1373
1374 def __del__(self) :
1375 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1376 self.__fd1.close()
1377 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1378 self.__fd2.close()
1379
1380 def write(self, text) :
1381 self.__fd1.write(text)
1382 self.__fd2.write(text)
1383
1384 def flush(self) :
1385 self.__fd1.flush()
1386 self.__fd2.flush()
1387
initial.commit94958cf2008-07-26 22:42:52 +00001388def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001389 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001390 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1391 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001392
1393 port = options.port
1394
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001395 server_data = {}
1396
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001397 if options.server_type == SERVER_HTTP:
1398 if options.cert:
1399 # let's make sure the cert file exists.
1400 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001401 print 'specified server cert file not found: ' + options.cert + \
1402 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001403 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001404 for ca_cert in options.ssl_client_ca:
1405 if not os.path.isfile(ca_cert):
1406 print 'specified trusted client CA file not found: ' + ca_cert + \
1407 ' exiting...'
1408 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001409 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001410 options.ssl_client_auth, options.ssl_client_ca,
1411 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001412 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001413 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001414 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001415 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001416
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001417 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001418 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001419 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001420 server._device_management_handler = None
akalin@chromium.org154bb132010-11-12 02:20:27 +00001421 elif options.server_type == SERVER_SYNC:
1422 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1423 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001424 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1425 server_data['port'] = server.server_port
1426 server_data['xmpp_port'] = server.xmpp_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001427 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001428 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001429 my_data_dir = MakeDataDir()
1430
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001431 # Instantiate a dummy authorizer for managing 'virtual' users
1432 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1433
1434 # Define a new user having full r/w permissions and a read-only
1435 # anonymous user
1436 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1437
1438 authorizer.add_anonymous(my_data_dir)
1439
1440 # Instantiate FTP handler class
1441 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1442 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001443
1444 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001445 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1446 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001447
1448 # Instantiate FTP server class and listen to 127.0.0.1:port
1449 address = ('127.0.0.1', port)
1450 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001451 server_data['port'] = server.socket.getsockname()[1]
1452 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001453
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001454 # Notify the parent that we've started. (BaseServer subclasses
1455 # bind their sockets on construction.)
1456 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001457 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001458 server_data_len = len(server_data_json)
1459 print 'sending server_data: %s (%d bytes)' % (
1460 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001461 if sys.platform == 'win32':
1462 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1463 else:
1464 fd = options.startup_pipe
1465 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001466 # First write the data length as an unsigned 4-byte value. This
1467 # is _not_ using network byte ordering since the other end of the
1468 # pipe is on the same machine.
1469 startup_pipe.write(struct.pack('=L', server_data_len))
1470 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001471 startup_pipe.close()
1472
initial.commit94958cf2008-07-26 22:42:52 +00001473 try:
1474 server.serve_forever()
1475 except KeyboardInterrupt:
1476 print 'shutting down server'
1477 server.stop = True
1478
1479if __name__ == '__main__':
1480 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001481 option_parser.add_option("-f", '--ftp', action='store_const',
1482 const=SERVER_FTP, default=SERVER_HTTP,
1483 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001484 help='start up an FTP server.')
1485 option_parser.add_option('', '--sync', action='store_const',
1486 const=SERVER_SYNC, default=SERVER_HTTP,
1487 dest='server_type',
1488 help='start up a sync server.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001489 option_parser.add_option('', '--port', default='0', type='int',
1490 help='Port used by the server. If unspecified, the '
1491 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001492 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001493 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001494 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001495 help='Specify that https should be used, specify '
1496 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001497 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001498 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1499 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001500 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1501 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001502 'should include the CA named in the subject of '
1503 'the DER-encoded certificate contained in the '
1504 'specified file. This option may appear multiple '
1505 'times, indicating multiple CA names should be '
1506 'sent in the request.')
1507 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1508 help='Specify the bulk encryption algorithm(s)'
1509 'that will be accepted by the SSL server. Valid '
1510 'values are "aes256", "aes128", "3des", "rc4". If '
1511 'omitted, all algorithms will be used. This '
1512 'option may appear multiple times, indicating '
1513 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001514 option_parser.add_option('', '--file-root-url', default='/files/',
1515 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001516 option_parser.add_option('', '--startup-pipe', type='int',
1517 dest='startup_pipe',
1518 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001519 options, args = option_parser.parse_args()
1520
1521 sys.exit(main(options, args))