blob: 36e0a897f6ce314847675e8217bc55d4116a9173 [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.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,
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 """The only difference between this function and the EchoHeaderOverride"""
559 """function is in the parameter being passed to the helper function"""
560 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000561
ananta@chromium.org219b2062009-10-23 16:09:41 +0000562 def EchoHeaderOverride(self):
563 """This handler echoes back the value of a specific request header."""
564 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
565 """IE to issue HTTP requests using the host network stack."""
566 """The Accept and Charset tests which expect the server to echo back"""
567 """the corresponding headers fail here as IE returns cached responses"""
568 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
569 """treats this request as a new request and does not cache it."""
570 return self.EchoHeaderHelper("/echoheaderoverride")
571
572 def EchoHeaderHelper(self, echo_header):
573 """This function echoes back the value of the request header passed in."""
574 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000575 return False
576
577 query_char = self.path.find('?')
578 if query_char != -1:
579 header_name = self.path[query_char+1:]
580
581 self.send_response(200)
582 self.send_header('Content-type', 'text/plain')
583 self.send_header('Cache-control', 'max-age=60000')
584 # insert a vary header to properly indicate that the cachability of this
585 # request is subject to value of the request header being echoed.
586 if len(header_name) > 0:
587 self.send_header('Vary', header_name)
588 self.end_headers()
589
590 if len(header_name) > 0:
591 self.wfile.write(self.headers.getheader(header_name))
592
593 return True
594
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000595 def ReadRequestBody(self):
596 """This function reads the body of the current HTTP request, handling
597 both plain and chunked transfer encoded requests."""
598
599 if self.headers.getheader('transfer-encoding') != 'chunked':
600 length = int(self.headers.getheader('content-length'))
601 return self.rfile.read(length)
602
603 # Read the request body as chunks.
604 body = ""
605 while True:
606 line = self.rfile.readline()
607 length = int(line, 16)
608 if length == 0:
609 self.rfile.readline()
610 break
611 body += self.rfile.read(length)
612 self.rfile.read(2)
613 return body
614
initial.commit94958cf2008-07-26 22:42:52 +0000615 def EchoHandler(self):
616 """This handler just echoes back the payload of the request, for testing
617 form submission."""
618
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000619 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000620 return False
621
622 self.send_response(200)
623 self.send_header('Content-type', 'text/html')
624 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000625 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000626 return True
627
628 def EchoTitleHandler(self):
629 """This handler is like Echo, but sets the page title to the request."""
630
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000631 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000632 return False
633
634 self.send_response(200)
635 self.send_header('Content-type', 'text/html')
636 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000637 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000638 self.wfile.write('<html><head><title>')
639 self.wfile.write(request)
640 self.wfile.write('</title></head></html>')
641 return True
642
643 def EchoAllHandler(self):
644 """This handler yields a (more) human-readable page listing information
645 about the request header & contents."""
646
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000647 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000648 return False
649
650 self.send_response(200)
651 self.send_header('Content-type', 'text/html')
652 self.end_headers()
653 self.wfile.write('<html><head><style>'
654 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
655 '</style></head><body>'
656 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000657 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000658 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000659
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000660 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000661 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000662 params = cgi.parse_qs(qs, keep_blank_values=1)
663
664 for param in params:
665 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000666
667 self.wfile.write('</pre>')
668
669 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
670
671 self.wfile.write('</body></html>')
672 return True
673
674 def DownloadHandler(self):
675 """This handler sends a downloadable file with or without reporting
676 the size (6K)."""
677
678 if self.path.startswith("/download-unknown-size"):
679 send_length = False
680 elif self.path.startswith("/download-known-size"):
681 send_length = True
682 else:
683 return False
684
685 #
686 # The test which uses this functionality is attempting to send
687 # small chunks of data to the client. Use a fairly large buffer
688 # so that we'll fill chrome's IO buffer enough to force it to
689 # actually write the data.
690 # See also the comments in the client-side of this test in
691 # download_uitest.cc
692 #
693 size_chunk1 = 35*1024
694 size_chunk2 = 10*1024
695
696 self.send_response(200)
697 self.send_header('Content-type', 'application/octet-stream')
698 self.send_header('Cache-Control', 'max-age=0')
699 if send_length:
700 self.send_header('Content-Length', size_chunk1 + size_chunk2)
701 self.end_headers()
702
703 # First chunk of data:
704 self.wfile.write("*" * size_chunk1)
705 self.wfile.flush()
706
707 # handle requests until one of them clears this flag.
708 self.server.waitForDownload = True
709 while self.server.waitForDownload:
710 self.server.handle_request()
711
712 # Second chunk of data:
713 self.wfile.write("*" * size_chunk2)
714 return True
715
716 def DownloadFinishHandler(self):
717 """This handler just tells the server to finish the current download."""
718
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000719 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000720 return False
721
722 self.server.waitForDownload = False
723 self.send_response(200)
724 self.send_header('Content-type', 'text/html')
725 self.send_header('Cache-Control', 'max-age=0')
726 self.end_headers()
727 return True
728
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000729 def _ReplaceFileData(self, data, query_parameters):
730 """Replaces matching substrings in a file.
731
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000732 If the 'replace_text' URL query parameter is present, it is expected to be
733 of the form old_text:new_text, which indicates that any old_text strings in
734 the file are replaced with new_text. Multiple 'replace_text' parameters may
735 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000736
737 If the parameters are not present, |data| is returned.
738 """
739 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000740 replace_text_values = query_dict.get('replace_text', [])
741 for replace_text_value in replace_text_values:
742 replace_text_args = replace_text_value.split(':')
743 if len(replace_text_args) != 2:
744 raise ValueError(
745 'replace_text must be of form old_text:new_text. Actual value: %s' %
746 replace_text_value)
747 old_text_b64, new_text_b64 = replace_text_args
748 old_text = base64.urlsafe_b64decode(old_text_b64)
749 new_text = base64.urlsafe_b64decode(new_text_b64)
750 data = data.replace(old_text, new_text)
751 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000752
initial.commit94958cf2008-07-26 22:42:52 +0000753 def FileHandler(self):
754 """This handler sends the contents of the requested file. Wow, it's like
755 a real webserver!"""
756
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000757 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000758 if not self.path.startswith(prefix):
759 return False
760
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000761 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000762 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000763 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000764
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000765 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
766 sub_path = url_path[len(prefix):]
767 entries = sub_path.split('/')
768 file_path = os.path.join(self.server.data_dir, *entries)
769 if os.path.isdir(file_path):
770 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000771
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000772 if not os.path.isfile(file_path):
773 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000774 self.send_error(404)
775 return True
776
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000777 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000778 data = f.read()
779 f.close()
780
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000781 data = self._ReplaceFileData(data, query)
782
initial.commit94958cf2008-07-26 22:42:52 +0000783 # If file.mock-http-headers exists, it contains the headers we
784 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000785 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000786 if os.path.isfile(headers_path):
787 f = open(headers_path, "r")
788
789 # "HTTP/1.1 200 OK"
790 response = f.readline()
791 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
792 self.send_response(int(status_code))
793
794 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000795 header_values = re.findall('(\S+):\s*(.*)', line)
796 if len(header_values) > 0:
797 # "name: value"
798 name, value = header_values[0]
799 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000800 f.close()
801 else:
802 # Could be more generic once we support mime-type sniffing, but for
803 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000804
805 range = self.headers.get('Range')
806 if range and range.startswith('bytes='):
807 # Note this doesn't handle all valid byte range values (i.e. open ended
808 # ones), just enough for what we needed so far.
809 range = range[6:].split('-')
810 start = int(range[0])
811 end = int(range[1])
812
813 self.send_response(206)
814 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
815 str(len(data))
816 self.send_header('Content-Range', content_range)
817 data = data[start: end + 1]
818 else:
819 self.send_response(200)
820
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000821 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000822 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000823 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000824 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000825 self.end_headers()
826
827 self.wfile.write(data)
828
829 return True
830
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000831 def SetCookieHandler(self):
832 """This handler just sets a cookie, for testing cookie handling."""
833
834 if not self._ShouldHandleRequest("/set-cookie"):
835 return False
836
837 query_char = self.path.find('?')
838 if query_char != -1:
839 cookie_values = self.path[query_char + 1:].split('&')
840 else:
841 cookie_values = ("",)
842 self.send_response(200)
843 self.send_header('Content-type', 'text/html')
844 for cookie_value in cookie_values:
845 self.send_header('Set-Cookie', '%s' % cookie_value)
846 self.end_headers()
847 for cookie_value in cookie_values:
848 self.wfile.write('%s' % cookie_value)
849 return True
850
initial.commit94958cf2008-07-26 22:42:52 +0000851 def AuthBasicHandler(self):
852 """This handler tests 'Basic' authentication. It just sends a page with
853 title 'user/pass' if you succeed."""
854
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000855 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000856 return False
857
858 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000859 expected_password = 'secret'
860 realm = 'testrealm'
861 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000862
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000863 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
864 query_params = cgi.parse_qs(query, True)
865 if 'set-cookie-if-challenged' in query_params:
866 set_cookie_if_challenged = True
867 if 'password' in query_params:
868 expected_password = query_params['password'][0]
869 if 'realm' in query_params:
870 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000871
initial.commit94958cf2008-07-26 22:42:52 +0000872 auth = self.headers.getheader('authorization')
873 try:
874 if not auth:
875 raise Exception('no auth')
876 b64str = re.findall(r'Basic (\S+)', auth)[0]
877 userpass = base64.b64decode(b64str)
878 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000879 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000880 raise Exception('wrong password')
881 except Exception, e:
882 # Authentication failed.
883 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000884 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000885 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000886 if set_cookie_if_challenged:
887 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000888 self.end_headers()
889 self.wfile.write('<html><head>')
890 self.wfile.write('<title>Denied: %s</title>' % e)
891 self.wfile.write('</head><body>')
892 self.wfile.write('auth=%s<p>' % auth)
893 self.wfile.write('b64str=%s<p>' % b64str)
894 self.wfile.write('username: %s<p>' % username)
895 self.wfile.write('userpass: %s<p>' % userpass)
896 self.wfile.write('password: %s<p>' % password)
897 self.wfile.write('You sent:<br>%s<p>' % self.headers)
898 self.wfile.write('</body></html>')
899 return True
900
901 # Authentication successful. (Return a cachable response to allow for
902 # testing cached pages that require authentication.)
903 if_none_match = self.headers.getheader('if-none-match')
904 if if_none_match == "abc":
905 self.send_response(304)
906 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000907 elif url_path.endswith(".gif"):
908 # Using chrome/test/data/google/logo.gif as the test image
909 test_image_path = ['google', 'logo.gif']
910 gif_path = os.path.join(self.server.data_dir, *test_image_path)
911 if not os.path.isfile(gif_path):
912 self.send_error(404)
913 return True
914
915 f = open(gif_path, "rb")
916 data = f.read()
917 f.close()
918
919 self.send_response(200)
920 self.send_header('Content-type', 'image/gif')
921 self.send_header('Cache-control', 'max-age=60000')
922 self.send_header('Etag', 'abc')
923 self.end_headers()
924 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000925 else:
926 self.send_response(200)
927 self.send_header('Content-type', 'text/html')
928 self.send_header('Cache-control', 'max-age=60000')
929 self.send_header('Etag', 'abc')
930 self.end_headers()
931 self.wfile.write('<html><head>')
932 self.wfile.write('<title>%s/%s</title>' % (username, password))
933 self.wfile.write('</head><body>')
934 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000935 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000936 self.wfile.write('</body></html>')
937
938 return True
939
tonyg@chromium.org75054202010-03-31 22:06:10 +0000940 def GetNonce(self, force_reset=False):
941 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000942
tonyg@chromium.org75054202010-03-31 22:06:10 +0000943 This is a fake implementation. A real implementation would only use a given
944 nonce a single time (hence the name n-once). However, for the purposes of
945 unittesting, we don't care about the security of the nonce.
946
947 Args:
948 force_reset: Iff set, the nonce will be changed. Useful for testing the
949 "stale" response.
950 """
951 if force_reset or not self.server.nonce_time:
952 self.server.nonce_time = time.time()
953 return _new_md5('privatekey%s%d' %
954 (self.path, self.server.nonce_time)).hexdigest()
955
956 def AuthDigestHandler(self):
957 """This handler tests 'Digest' authentication.
958
959 It just sends a page with title 'user/pass' if you succeed.
960
961 A stale response is sent iff "stale" is present in the request path.
962 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000963 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000964 return False
965
tonyg@chromium.org75054202010-03-31 22:06:10 +0000966 stale = 'stale' in self.path
967 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000968 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000969 password = 'secret'
970 realm = 'testrealm'
971
972 auth = self.headers.getheader('authorization')
973 pairs = {}
974 try:
975 if not auth:
976 raise Exception('no auth')
977 if not auth.startswith('Digest'):
978 raise Exception('not digest')
979 # Pull out all the name="value" pairs as a dictionary.
980 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
981
982 # Make sure it's all valid.
983 if pairs['nonce'] != nonce:
984 raise Exception('wrong nonce')
985 if pairs['opaque'] != opaque:
986 raise Exception('wrong opaque')
987
988 # Check the 'response' value and make sure it matches our magic hash.
989 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000990 hash_a1 = _new_md5(
991 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000992 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000993 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000994 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000995 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
996 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000997 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000998
999 if pairs['response'] != response:
1000 raise Exception('wrong password')
1001 except Exception, e:
1002 # Authentication failed.
1003 self.send_response(401)
1004 hdr = ('Digest '
1005 'realm="%s", '
1006 'domain="/", '
1007 'qop="auth", '
1008 'algorithm=MD5, '
1009 'nonce="%s", '
1010 'opaque="%s"') % (realm, nonce, opaque)
1011 if stale:
1012 hdr += ', stale="TRUE"'
1013 self.send_header('WWW-Authenticate', hdr)
1014 self.send_header('Content-type', 'text/html')
1015 self.end_headers()
1016 self.wfile.write('<html><head>')
1017 self.wfile.write('<title>Denied: %s</title>' % e)
1018 self.wfile.write('</head><body>')
1019 self.wfile.write('auth=%s<p>' % auth)
1020 self.wfile.write('pairs=%s<p>' % pairs)
1021 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1022 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1023 self.wfile.write('</body></html>')
1024 return True
1025
1026 # Authentication successful.
1027 self.send_response(200)
1028 self.send_header('Content-type', 'text/html')
1029 self.end_headers()
1030 self.wfile.write('<html><head>')
1031 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1032 self.wfile.write('</head><body>')
1033 self.wfile.write('auth=%s<p>' % auth)
1034 self.wfile.write('pairs=%s<p>' % pairs)
1035 self.wfile.write('</body></html>')
1036
1037 return True
1038
1039 def SlowServerHandler(self):
1040 """Wait for the user suggested time before responding. The syntax is
1041 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001042 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001043 return False
1044 query_char = self.path.find('?')
1045 wait_sec = 1.0
1046 if query_char >= 0:
1047 try:
1048 wait_sec = int(self.path[query_char + 1:])
1049 except ValueError:
1050 pass
1051 time.sleep(wait_sec)
1052 self.send_response(200)
1053 self.send_header('Content-type', 'text/plain')
1054 self.end_headers()
1055 self.wfile.write("waited %d seconds" % wait_sec)
1056 return True
1057
1058 def ContentTypeHandler(self):
1059 """Returns a string of html with the given content type. E.g.,
1060 /contenttype?text/css returns an html file with the Content-Type
1061 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001062 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001063 return False
1064 query_char = self.path.find('?')
1065 content_type = self.path[query_char + 1:].strip()
1066 if not content_type:
1067 content_type = 'text/html'
1068 self.send_response(200)
1069 self.send_header('Content-Type', content_type)
1070 self.end_headers()
1071 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1072 return True
1073
creis@google.com2f4f6a42011-03-25 19:44:19 +00001074 def NoContentHandler(self):
1075 """Returns a 204 No Content response."""
1076 if not self._ShouldHandleRequest("/nocontent"):
1077 return False
1078 self.send_response(204)
1079 self.end_headers()
1080 return True
1081
initial.commit94958cf2008-07-26 22:42:52 +00001082 def ServerRedirectHandler(self):
1083 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001084 '/server-redirect?http://foo.bar/asdf' to redirect to
1085 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001086
1087 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001088 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001089 return False
1090
1091 query_char = self.path.find('?')
1092 if query_char < 0 or len(self.path) <= query_char + 1:
1093 self.sendRedirectHelp(test_name)
1094 return True
1095 dest = self.path[query_char + 1:]
1096
1097 self.send_response(301) # moved permanently
1098 self.send_header('Location', dest)
1099 self.send_header('Content-type', 'text/html')
1100 self.end_headers()
1101 self.wfile.write('<html><head>')
1102 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1103
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001104 return True
initial.commit94958cf2008-07-26 22:42:52 +00001105
1106 def ClientRedirectHandler(self):
1107 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001108 '/client-redirect?http://foo.bar/asdf' to redirect to
1109 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001110
1111 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001112 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001113 return False
1114
1115 query_char = self.path.find('?');
1116 if query_char < 0 or len(self.path) <= query_char + 1:
1117 self.sendRedirectHelp(test_name)
1118 return True
1119 dest = self.path[query_char + 1:]
1120
1121 self.send_response(200)
1122 self.send_header('Content-type', 'text/html')
1123 self.end_headers()
1124 self.wfile.write('<html><head>')
1125 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1126 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1127
1128 return True
1129
tony@chromium.org03266982010-03-05 03:18:42 +00001130 def MultipartHandler(self):
1131 """Send a multipart response (10 text/html pages)."""
1132 test_name = "/multipart"
1133 if not self._ShouldHandleRequest(test_name):
1134 return False
1135
1136 num_frames = 10
1137 bound = '12345'
1138 self.send_response(200)
1139 self.send_header('Content-type',
1140 'multipart/x-mixed-replace;boundary=' + bound)
1141 self.end_headers()
1142
1143 for i in xrange(num_frames):
1144 self.wfile.write('--' + bound + '\r\n')
1145 self.wfile.write('Content-type: text/html\r\n\r\n')
1146 self.wfile.write('<title>page ' + str(i) + '</title>')
1147 self.wfile.write('page ' + str(i))
1148
1149 self.wfile.write('--' + bound + '--')
1150 return True
1151
initial.commit94958cf2008-07-26 22:42:52 +00001152 def DefaultResponseHandler(self):
1153 """This is the catch-all response handler for requests that aren't handled
1154 by one of the special handlers above.
1155 Note that we specify the content-length as without it the https connection
1156 is not closed properly (and the browser keeps expecting data)."""
1157
1158 contents = "Default response given for path: " + self.path
1159 self.send_response(200)
1160 self.send_header('Content-type', 'text/html')
1161 self.send_header("Content-Length", len(contents))
1162 self.end_headers()
1163 self.wfile.write(contents)
1164 return True
1165
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001166 def RedirectConnectHandler(self):
1167 """Sends a redirect to the CONNECT request for www.redirect.com. This
1168 response is not specified by the RFC, so the browser should not follow
1169 the redirect."""
1170
1171 if (self.path.find("www.redirect.com") < 0):
1172 return False
1173
1174 dest = "http://www.destination.com/foo.js"
1175
1176 self.send_response(302) # moved temporarily
1177 self.send_header('Location', dest)
1178 self.send_header('Connection', 'close')
1179 self.end_headers()
1180 return True
1181
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001182 def ServerAuthConnectHandler(self):
1183 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1184 response doesn't make sense because the proxy server cannot request
1185 server authentication."""
1186
1187 if (self.path.find("www.server-auth.com") < 0):
1188 return False
1189
1190 challenge = 'Basic realm="WallyWorld"'
1191
1192 self.send_response(401) # unauthorized
1193 self.send_header('WWW-Authenticate', challenge)
1194 self.send_header('Connection', 'close')
1195 self.end_headers()
1196 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001197
1198 def DefaultConnectResponseHandler(self):
1199 """This is the catch-all response handler for CONNECT requests that aren't
1200 handled by one of the special handlers above. Real Web servers respond
1201 with 400 to CONNECT requests."""
1202
1203 contents = "Your client has issued a malformed or illegal request."
1204 self.send_response(400) # bad request
1205 self.send_header('Content-type', 'text/html')
1206 self.send_header("Content-Length", len(contents))
1207 self.end_headers()
1208 self.wfile.write(contents)
1209 return True
1210
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001211 def DeviceManagementHandler(self):
1212 """Delegates to the device management service used for cloud policy."""
1213 if not self._ShouldHandleRequest("/device_management"):
1214 return False
1215
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001216 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001217
1218 if not self.server._device_management_handler:
1219 import device_management
1220 policy_path = os.path.join(self.server.data_dir, 'device_management')
1221 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001222 device_management.TestServer(policy_path,
1223 self.server.policy_cert_chain))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001224
1225 http_response, raw_reply = (
1226 self.server._device_management_handler.HandleRequest(self.path,
1227 self.headers,
1228 raw_request))
1229 self.send_response(http_response)
1230 self.end_headers()
1231 self.wfile.write(raw_reply)
1232 return True
1233
initial.commit94958cf2008-07-26 22:42:52 +00001234 # called by the redirect handling function when there is no parameter
1235 def sendRedirectHelp(self, redirect_name):
1236 self.send_response(200)
1237 self.send_header('Content-type', 'text/html')
1238 self.end_headers()
1239 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1240 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1241 self.wfile.write('</body></html>')
1242
akalin@chromium.org154bb132010-11-12 02:20:27 +00001243
1244class SyncPageHandler(BasePageHandler):
1245 """Handler for the main HTTP sync server."""
1246
1247 def __init__(self, request, client_address, sync_http_server):
1248 get_handlers = [self.ChromiumSyncTimeHandler]
1249 post_handlers = [self.ChromiumSyncCommandHandler]
1250 BasePageHandler.__init__(self, request, client_address,
1251 sync_http_server, [], get_handlers,
1252 post_handlers, [])
1253
1254 def ChromiumSyncTimeHandler(self):
1255 """Handle Chromium sync .../time requests.
1256
1257 The syncer sometimes checks server reachability by examining /time.
1258 """
1259 test_name = "/chromiumsync/time"
1260 if not self._ShouldHandleRequest(test_name):
1261 return False
1262
1263 self.send_response(200)
1264 self.send_header('Content-type', 'text/html')
1265 self.end_headers()
1266 return True
1267
1268 def ChromiumSyncCommandHandler(self):
1269 """Handle a chromiumsync command arriving via http.
1270
1271 This covers all sync protocol commands: authentication, getupdates, and
1272 commit.
1273 """
1274 test_name = "/chromiumsync/command"
1275 if not self._ShouldHandleRequest(test_name):
1276 return False
1277
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001278 length = int(self.headers.getheader('content-length'))
1279 raw_request = self.rfile.read(length)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001280
1281 http_response, raw_reply = self.server.HandleCommand(
1282 self.path, raw_request)
1283 self.send_response(http_response)
1284 self.end_headers()
1285 self.wfile.write(raw_reply)
1286 return True
1287
1288
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001289def MakeDataDir():
1290 if options.data_dir:
1291 if not os.path.isdir(options.data_dir):
1292 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1293 return None
1294 my_data_dir = options.data_dir
1295 else:
1296 # Create the default path to our data dir, relative to the exe dir.
1297 my_data_dir = os.path.dirname(sys.argv[0])
1298 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001299 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001300
1301 #TODO(ibrar): Must use Find* funtion defined in google\tools
1302 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1303
1304 return my_data_dir
1305
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001306class FileMultiplexer:
1307 def __init__(self, fd1, fd2) :
1308 self.__fd1 = fd1
1309 self.__fd2 = fd2
1310
1311 def __del__(self) :
1312 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1313 self.__fd1.close()
1314 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1315 self.__fd2.close()
1316
1317 def write(self, text) :
1318 self.__fd1.write(text)
1319 self.__fd2.write(text)
1320
1321 def flush(self) :
1322 self.__fd1.flush()
1323 self.__fd2.flush()
1324
initial.commit94958cf2008-07-26 22:42:52 +00001325def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001326 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001327 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001328 if options.log_to_console:
1329 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1330 else:
1331 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001332
1333 port = options.port
1334
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001335 server_data = {}
1336
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001337 if options.server_type == SERVER_HTTP:
1338 if options.cert:
1339 # let's make sure the cert file exists.
1340 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001341 print 'specified server cert file not found: ' + options.cert + \
1342 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001343 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001344 for ca_cert in options.ssl_client_ca:
1345 if not os.path.isfile(ca_cert):
1346 print 'specified trusted client CA file not found: ' + ca_cert + \
1347 ' exiting...'
1348 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001349 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001350 options.ssl_client_auth, options.ssl_client_ca,
1351 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001352 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001353 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001354 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001355 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001356
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001357 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001358 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001359 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001360 server._device_management_handler = None
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001361 server.policy_cert_chain = options.policy_cert_chain
akalin@chromium.org154bb132010-11-12 02:20:27 +00001362 elif options.server_type == SERVER_SYNC:
1363 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1364 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001365 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1366 server_data['port'] = server.server_port
1367 server_data['xmpp_port'] = server.xmpp_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001368 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001369 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001370 my_data_dir = MakeDataDir()
1371
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001372 # Instantiate a dummy authorizer for managing 'virtual' users
1373 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1374
1375 # Define a new user having full r/w permissions and a read-only
1376 # anonymous user
1377 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1378
1379 authorizer.add_anonymous(my_data_dir)
1380
1381 # Instantiate FTP handler class
1382 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1383 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001384
1385 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001386 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1387 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001388
1389 # Instantiate FTP server class and listen to 127.0.0.1:port
1390 address = ('127.0.0.1', port)
1391 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001392 server_data['port'] = server.socket.getsockname()[1]
1393 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001394
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001395 # Notify the parent that we've started. (BaseServer subclasses
1396 # bind their sockets on construction.)
1397 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001398 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001399 server_data_len = len(server_data_json)
1400 print 'sending server_data: %s (%d bytes)' % (
1401 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001402 if sys.platform == 'win32':
1403 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1404 else:
1405 fd = options.startup_pipe
1406 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001407 # First write the data length as an unsigned 4-byte value. This
1408 # is _not_ using network byte ordering since the other end of the
1409 # pipe is on the same machine.
1410 startup_pipe.write(struct.pack('=L', server_data_len))
1411 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001412 startup_pipe.close()
1413
initial.commit94958cf2008-07-26 22:42:52 +00001414 try:
1415 server.serve_forever()
1416 except KeyboardInterrupt:
1417 print 'shutting down server'
1418 server.stop = True
1419
1420if __name__ == '__main__':
1421 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001422 option_parser.add_option("-f", '--ftp', action='store_const',
1423 const=SERVER_FTP, default=SERVER_HTTP,
1424 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001425 help='start up an FTP server.')
1426 option_parser.add_option('', '--sync', action='store_const',
1427 const=SERVER_SYNC, default=SERVER_HTTP,
1428 dest='server_type',
1429 help='start up a sync server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001430 option_parser.add_option('', '--log-to-console', action='store_const',
1431 const=True, default=False,
1432 dest='log_to_console',
1433 help='Enables or disables sys.stdout logging to '
1434 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001435 option_parser.add_option('', '--port', default='0', type='int',
1436 help='Port used by the server. If unspecified, the '
1437 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001438 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001439 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001440 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001441 help='Specify that https should be used, specify '
1442 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001443 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001444 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1445 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001446 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1447 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001448 'should include the CA named in the subject of '
1449 'the DER-encoded certificate contained in the '
1450 'specified file. This option may appear multiple '
1451 'times, indicating multiple CA names should be '
1452 'sent in the request.')
1453 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1454 help='Specify the bulk encryption algorithm(s)'
1455 'that will be accepted by the SSL server. Valid '
1456 'values are "aes256", "aes128", "3des", "rc4". If '
1457 'omitted, all algorithms will be used. This '
1458 'option may appear multiple times, indicating '
1459 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001460 option_parser.add_option('', '--file-root-url', default='/files/',
1461 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001462 option_parser.add_option('', '--startup-pipe', type='int',
1463 dest='startup_pipe',
1464 help='File handle of pipe to parent process')
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001465 option_parser.add_option('', '--policy-cert-chain', action='append',
1466 help='Specify a path to a certificate file to sign '
1467 'policy responses. This option may be used '
1468 'multiple times to define a certificate chain. '
1469 'The first element will be used for signing, '
1470 'the last element should be the root '
1471 'certificate.')
initial.commit94958cf2008-07-26 22:42:52 +00001472 options, args = option_parser.parse_args()
1473
1474 sys.exit(main(options, args))