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