blob: 2411f56d0782fd76257421755329968ed9af8ae9 [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,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000288 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000289 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000290 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000291 self.ServerRedirectHandler,
292 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000293 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000294 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000295 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000296 self.EchoTitleHandler,
297 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000298 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000299 self.DeviceManagementHandler] + get_handlers
300 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000301 self.EchoTitleHandler,
302 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000304
maruel@google.come250a9b2009-03-10 17:39:46 +0000305 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000306 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000307 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000308 'gif': 'image/gif',
309 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000310 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000311 'pdf' : 'application/pdf',
312 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000313 }
initial.commit94958cf2008-07-26 22:42:52 +0000314 self._default_mime_type = 'text/html'
315
akalin@chromium.org154bb132010-11-12 02:20:27 +0000316 BasePageHandler.__init__(self, request, client_address, socket_server,
317 connect_handlers, get_handlers, post_handlers,
318 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000319
initial.commit94958cf2008-07-26 22:42:52 +0000320 def GetMIMETypeFromName(self, file_name):
321 """Returns the mime type for the specified file_name. So far it only looks
322 at the file extension."""
323
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000324 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000325 if len(extension) == 0:
326 # no extension.
327 return self._default_mime_type
328
ericroman@google.comc17ca532009-05-07 03:51:05 +0000329 # extension starts with a dot, so we need to remove it
330 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000331
initial.commit94958cf2008-07-26 22:42:52 +0000332 def NoCacheMaxAgeTimeHandler(self):
333 """This request handler yields a page with the title set to the current
334 system time, and no caching requested."""
335
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000336 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000337 return False
338
339 self.send_response(200)
340 self.send_header('Cache-Control', 'max-age=0')
341 self.send_header('Content-type', 'text/html')
342 self.end_headers()
343
maruel@google.come250a9b2009-03-10 17:39:46 +0000344 self.wfile.write('<html><head><title>%s</title></head></html>' %
345 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000346
347 return True
348
349 def NoCacheTimeHandler(self):
350 """This request handler yields a page with the title set to the current
351 system time, and no caching requested."""
352
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000353 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000354 return False
355
356 self.send_response(200)
357 self.send_header('Cache-Control', 'no-cache')
358 self.send_header('Content-type', 'text/html')
359 self.end_headers()
360
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 self.wfile.write('<html><head><title>%s</title></head></html>' %
362 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000363
364 return True
365
366 def CacheTimeHandler(self):
367 """This request handler yields a page with the title set to the current
368 system time, and allows caching for one minute."""
369
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000370 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000371 return False
372
373 self.send_response(200)
374 self.send_header('Cache-Control', 'max-age=60')
375 self.send_header('Content-type', 'text/html')
376 self.end_headers()
377
maruel@google.come250a9b2009-03-10 17:39:46 +0000378 self.wfile.write('<html><head><title>%s</title></head></html>' %
379 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000380
381 return True
382
383 def CacheExpiresHandler(self):
384 """This request handler yields a page with the title set to the current
385 system time, and set the page to expire on 1 Jan 2099."""
386
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000387 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000388 return False
389
390 self.send_response(200)
391 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
392 self.send_header('Content-type', 'text/html')
393 self.end_headers()
394
maruel@google.come250a9b2009-03-10 17:39:46 +0000395 self.wfile.write('<html><head><title>%s</title></head></html>' %
396 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000397
398 return True
399
400 def CacheProxyRevalidateHandler(self):
401 """This request handler yields a page with the title set to the current
402 system time, and allows caching for 60 seconds"""
403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
408 self.send_header('Content-type', 'text/html')
409 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417 def CachePrivateHandler(self):
418 """This request handler yields a page with the title set to the current
419 system time, and allows caching for 5 seconds."""
420
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000421 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000422 return False
423
424 self.send_response(200)
425 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000426 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000427 self.end_headers()
428
maruel@google.come250a9b2009-03-10 17:39:46 +0000429 self.wfile.write('<html><head><title>%s</title></head></html>' %
430 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000431
432 return True
433
434 def CachePublicHandler(self):
435 """This request handler yields a page with the title set to the current
436 system time, and allows caching for 5 seconds."""
437
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000438 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000439 return False
440
441 self.send_response(200)
442 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000443 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000444 self.end_headers()
445
maruel@google.come250a9b2009-03-10 17:39:46 +0000446 self.wfile.write('<html><head><title>%s</title></head></html>' %
447 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000448
449 return True
450
451 def CacheSMaxAgeHandler(self):
452 """This request handler yields a page with the title set to the current
453 system time, and does not allow for caching."""
454
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000455 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000456 return False
457
458 self.send_response(200)
459 self.send_header('Content-type', 'text/html')
460 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
461 self.end_headers()
462
maruel@google.come250a9b2009-03-10 17:39:46 +0000463 self.wfile.write('<html><head><title>%s</title></head></html>' %
464 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000465
466 return True
467
468 def CacheMustRevalidateHandler(self):
469 """This request handler yields a page with the title set to the current
470 system time, and does not allow caching."""
471
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000472 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000473 return False
474
475 self.send_response(200)
476 self.send_header('Content-type', 'text/html')
477 self.send_header('Cache-Control', 'must-revalidate')
478 self.end_headers()
479
maruel@google.come250a9b2009-03-10 17:39:46 +0000480 self.wfile.write('<html><head><title>%s</title></head></html>' %
481 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000482
483 return True
484
485 def CacheMustRevalidateMaxAgeHandler(self):
486 """This request handler yields a page with the title set to the current
487 system time, and does not allow caching event though max-age of 60
488 seconds is specified."""
489
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000490 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000491 return False
492
493 self.send_response(200)
494 self.send_header('Content-type', 'text/html')
495 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
496 self.end_headers()
497
maruel@google.come250a9b2009-03-10 17:39:46 +0000498 self.wfile.write('<html><head><title>%s</title></head></html>' %
499 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000500
501 return True
502
initial.commit94958cf2008-07-26 22:42:52 +0000503 def CacheNoStoreHandler(self):
504 """This request handler yields a page with the title set to the current
505 system time, and does not allow the page to be stored."""
506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
511 self.send_header('Content-type', 'text/html')
512 self.send_header('Cache-Control', 'no-store')
513 self.end_headers()
514
maruel@google.come250a9b2009-03-10 17:39:46 +0000515 self.wfile.write('<html><head><title>%s</title></head></html>' %
516 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000517
518 return True
519
520 def CacheNoStoreMaxAgeHandler(self):
521 """This request handler yields a page with the title set to the current
522 system time, and does not allow the page to be stored even though max-age
523 of 60 seconds is specified."""
524
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000525 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000526 return False
527
528 self.send_response(200)
529 self.send_header('Content-type', 'text/html')
530 self.send_header('Cache-Control', 'max-age=60, no-store')
531 self.end_headers()
532
maruel@google.come250a9b2009-03-10 17:39:46 +0000533 self.wfile.write('<html><head><title>%s</title></head></html>' %
534 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000535
536 return True
537
538
539 def CacheNoTransformHandler(self):
540 """This request handler yields a page with the title set to the current
541 system time, and does not allow the content to transformed during
542 user-agent caching"""
543
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000544 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000545 return False
546
547 self.send_response(200)
548 self.send_header('Content-type', 'text/html')
549 self.send_header('Cache-Control', 'no-transform')
550 self.end_headers()
551
maruel@google.come250a9b2009-03-10 17:39:46 +0000552 self.wfile.write('<html><head><title>%s</title></head></html>' %
553 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000554
555 return True
556
557 def EchoHeader(self):
558 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000559 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000560
ananta@chromium.org56812d02011-04-07 17:52:05 +0000561 """This function echoes back the value of a specific request header"""
562 """while allowing caching for 16 hours."""
563 def EchoHeaderCache(self):
564 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000565
566 def EchoHeaderHelper(self, echo_header):
567 """This function echoes back the value of the request header passed in."""
568 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000569 return False
570
571 query_char = self.path.find('?')
572 if query_char != -1:
573 header_name = self.path[query_char+1:]
574
575 self.send_response(200)
576 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000577 if echo_header == '/echoheadercache':
578 self.send_header('Cache-control', 'max-age=60000')
579 else:
580 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000581 # insert a vary header to properly indicate that the cachability of this
582 # request is subject to value of the request header being echoed.
583 if len(header_name) > 0:
584 self.send_header('Vary', header_name)
585 self.end_headers()
586
587 if len(header_name) > 0:
588 self.wfile.write(self.headers.getheader(header_name))
589
590 return True
591
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000592 def ReadRequestBody(self):
593 """This function reads the body of the current HTTP request, handling
594 both plain and chunked transfer encoded requests."""
595
596 if self.headers.getheader('transfer-encoding') != 'chunked':
597 length = int(self.headers.getheader('content-length'))
598 return self.rfile.read(length)
599
600 # Read the request body as chunks.
601 body = ""
602 while True:
603 line = self.rfile.readline()
604 length = int(line, 16)
605 if length == 0:
606 self.rfile.readline()
607 break
608 body += self.rfile.read(length)
609 self.rfile.read(2)
610 return body
611
initial.commit94958cf2008-07-26 22:42:52 +0000612 def EchoHandler(self):
613 """This handler just echoes back the payload of the request, for testing
614 form submission."""
615
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000616 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000617 return False
618
619 self.send_response(200)
620 self.send_header('Content-type', 'text/html')
621 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000622 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000623 return True
624
625 def EchoTitleHandler(self):
626 """This handler is like Echo, but sets the page title to the request."""
627
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000628 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000629 return False
630
631 self.send_response(200)
632 self.send_header('Content-type', 'text/html')
633 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000634 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000635 self.wfile.write('<html><head><title>')
636 self.wfile.write(request)
637 self.wfile.write('</title></head></html>')
638 return True
639
640 def EchoAllHandler(self):
641 """This handler yields a (more) human-readable page listing information
642 about the request header & contents."""
643
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000644 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000645 return False
646
647 self.send_response(200)
648 self.send_header('Content-type', 'text/html')
649 self.end_headers()
650 self.wfile.write('<html><head><style>'
651 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
652 '</style></head><body>'
653 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000654 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000655 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000656
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000657 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000658 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000659 params = cgi.parse_qs(qs, keep_blank_values=1)
660
661 for param in params:
662 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000663
664 self.wfile.write('</pre>')
665
666 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
667
668 self.wfile.write('</body></html>')
669 return True
670
671 def DownloadHandler(self):
672 """This handler sends a downloadable file with or without reporting
673 the size (6K)."""
674
675 if self.path.startswith("/download-unknown-size"):
676 send_length = False
677 elif self.path.startswith("/download-known-size"):
678 send_length = True
679 else:
680 return False
681
682 #
683 # The test which uses this functionality is attempting to send
684 # small chunks of data to the client. Use a fairly large buffer
685 # so that we'll fill chrome's IO buffer enough to force it to
686 # actually write the data.
687 # See also the comments in the client-side of this test in
688 # download_uitest.cc
689 #
690 size_chunk1 = 35*1024
691 size_chunk2 = 10*1024
692
693 self.send_response(200)
694 self.send_header('Content-type', 'application/octet-stream')
695 self.send_header('Cache-Control', 'max-age=0')
696 if send_length:
697 self.send_header('Content-Length', size_chunk1 + size_chunk2)
698 self.end_headers()
699
700 # First chunk of data:
701 self.wfile.write("*" * size_chunk1)
702 self.wfile.flush()
703
704 # handle requests until one of them clears this flag.
705 self.server.waitForDownload = True
706 while self.server.waitForDownload:
707 self.server.handle_request()
708
709 # Second chunk of data:
710 self.wfile.write("*" * size_chunk2)
711 return True
712
713 def DownloadFinishHandler(self):
714 """This handler just tells the server to finish the current download."""
715
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000716 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000717 return False
718
719 self.server.waitForDownload = False
720 self.send_response(200)
721 self.send_header('Content-type', 'text/html')
722 self.send_header('Cache-Control', 'max-age=0')
723 self.end_headers()
724 return True
725
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000726 def _ReplaceFileData(self, data, query_parameters):
727 """Replaces matching substrings in a file.
728
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000729 If the 'replace_text' URL query parameter is present, it is expected to be
730 of the form old_text:new_text, which indicates that any old_text strings in
731 the file are replaced with new_text. Multiple 'replace_text' parameters may
732 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000733
734 If the parameters are not present, |data| is returned.
735 """
736 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000737 replace_text_values = query_dict.get('replace_text', [])
738 for replace_text_value in replace_text_values:
739 replace_text_args = replace_text_value.split(':')
740 if len(replace_text_args) != 2:
741 raise ValueError(
742 'replace_text must be of form old_text:new_text. Actual value: %s' %
743 replace_text_value)
744 old_text_b64, new_text_b64 = replace_text_args
745 old_text = base64.urlsafe_b64decode(old_text_b64)
746 new_text = base64.urlsafe_b64decode(new_text_b64)
747 data = data.replace(old_text, new_text)
748 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000749
initial.commit94958cf2008-07-26 22:42:52 +0000750 def FileHandler(self):
751 """This handler sends the contents of the requested file. Wow, it's like
752 a real webserver!"""
753
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000754 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000755 if not self.path.startswith(prefix):
756 return False
757
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000758 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000759 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000760 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000761
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000762 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
763 sub_path = url_path[len(prefix):]
764 entries = sub_path.split('/')
765 file_path = os.path.join(self.server.data_dir, *entries)
766 if os.path.isdir(file_path):
767 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000768
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000769 if not os.path.isfile(file_path):
770 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000771 self.send_error(404)
772 return True
773
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000774 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000775 data = f.read()
776 f.close()
777
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000778 data = self._ReplaceFileData(data, query)
779
initial.commit94958cf2008-07-26 22:42:52 +0000780 # If file.mock-http-headers exists, it contains the headers we
781 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000782 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000783 if os.path.isfile(headers_path):
784 f = open(headers_path, "r")
785
786 # "HTTP/1.1 200 OK"
787 response = f.readline()
788 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
789 self.send_response(int(status_code))
790
791 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000792 header_values = re.findall('(\S+):\s*(.*)', line)
793 if len(header_values) > 0:
794 # "name: value"
795 name, value = header_values[0]
796 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000797 f.close()
798 else:
799 # Could be more generic once we support mime-type sniffing, but for
800 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000801
802 range = self.headers.get('Range')
803 if range and range.startswith('bytes='):
804 # Note this doesn't handle all valid byte range values (i.e. open ended
805 # ones), just enough for what we needed so far.
806 range = range[6:].split('-')
807 start = int(range[0])
808 end = int(range[1])
809
810 self.send_response(206)
811 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
812 str(len(data))
813 self.send_header('Content-Range', content_range)
814 data = data[start: end + 1]
815 else:
816 self.send_response(200)
817
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000818 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000819 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000820 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000821 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000822 self.end_headers()
823
824 self.wfile.write(data)
825
826 return True
827
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000828 def SetCookieHandler(self):
829 """This handler just sets a cookie, for testing cookie handling."""
830
831 if not self._ShouldHandleRequest("/set-cookie"):
832 return False
833
834 query_char = self.path.find('?')
835 if query_char != -1:
836 cookie_values = self.path[query_char + 1:].split('&')
837 else:
838 cookie_values = ("",)
839 self.send_response(200)
840 self.send_header('Content-type', 'text/html')
841 for cookie_value in cookie_values:
842 self.send_header('Set-Cookie', '%s' % cookie_value)
843 self.end_headers()
844 for cookie_value in cookie_values:
845 self.wfile.write('%s' % cookie_value)
846 return True
847
initial.commit94958cf2008-07-26 22:42:52 +0000848 def AuthBasicHandler(self):
849 """This handler tests 'Basic' authentication. It just sends a page with
850 title 'user/pass' if you succeed."""
851
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000852 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000853 return False
854
855 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000856 expected_password = 'secret'
857 realm = 'testrealm'
858 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000859
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000860 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
861 query_params = cgi.parse_qs(query, True)
862 if 'set-cookie-if-challenged' in query_params:
863 set_cookie_if_challenged = True
864 if 'password' in query_params:
865 expected_password = query_params['password'][0]
866 if 'realm' in query_params:
867 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000868
initial.commit94958cf2008-07-26 22:42:52 +0000869 auth = self.headers.getheader('authorization')
870 try:
871 if not auth:
872 raise Exception('no auth')
873 b64str = re.findall(r'Basic (\S+)', auth)[0]
874 userpass = base64.b64decode(b64str)
875 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000876 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000877 raise Exception('wrong password')
878 except Exception, e:
879 # Authentication failed.
880 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000881 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000882 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000883 if set_cookie_if_challenged:
884 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000885 self.end_headers()
886 self.wfile.write('<html><head>')
887 self.wfile.write('<title>Denied: %s</title>' % e)
888 self.wfile.write('</head><body>')
889 self.wfile.write('auth=%s<p>' % auth)
890 self.wfile.write('b64str=%s<p>' % b64str)
891 self.wfile.write('username: %s<p>' % username)
892 self.wfile.write('userpass: %s<p>' % userpass)
893 self.wfile.write('password: %s<p>' % password)
894 self.wfile.write('You sent:<br>%s<p>' % self.headers)
895 self.wfile.write('</body></html>')
896 return True
897
898 # Authentication successful. (Return a cachable response to allow for
899 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +0000900 old_protocol_version = self.protocol_version
901 self.protocol_version = "HTTP/1.1"
902
initial.commit94958cf2008-07-26 22:42:52 +0000903 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)
rvargas@google.com54453b72011-05-19 01:11:11 +0000913 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000914 return True
915
916 f = open(gif_path, "rb")
917 data = f.read()
918 f.close()
919
920 self.send_response(200)
921 self.send_header('Content-type', 'image/gif')
922 self.send_header('Cache-control', 'max-age=60000')
923 self.send_header('Etag', 'abc')
924 self.end_headers()
925 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000926 else:
927 self.send_response(200)
928 self.send_header('Content-type', 'text/html')
929 self.send_header('Cache-control', 'max-age=60000')
930 self.send_header('Etag', 'abc')
931 self.end_headers()
932 self.wfile.write('<html><head>')
933 self.wfile.write('<title>%s/%s</title>' % (username, password))
934 self.wfile.write('</head><body>')
935 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000936 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000937 self.wfile.write('</body></html>')
938
rvargas@google.com54453b72011-05-19 01:11:11 +0000939 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000940 return True
941
tonyg@chromium.org75054202010-03-31 22:06:10 +0000942 def GetNonce(self, force_reset=False):
943 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000944
tonyg@chromium.org75054202010-03-31 22:06:10 +0000945 This is a fake implementation. A real implementation would only use a given
946 nonce a single time (hence the name n-once). However, for the purposes of
947 unittesting, we don't care about the security of the nonce.
948
949 Args:
950 force_reset: Iff set, the nonce will be changed. Useful for testing the
951 "stale" response.
952 """
953 if force_reset or not self.server.nonce_time:
954 self.server.nonce_time = time.time()
955 return _new_md5('privatekey%s%d' %
956 (self.path, self.server.nonce_time)).hexdigest()
957
958 def AuthDigestHandler(self):
959 """This handler tests 'Digest' authentication.
960
961 It just sends a page with title 'user/pass' if you succeed.
962
963 A stale response is sent iff "stale" is present in the request path.
964 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000965 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000966 return False
967
tonyg@chromium.org75054202010-03-31 22:06:10 +0000968 stale = 'stale' in self.path
969 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000970 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000971 password = 'secret'
972 realm = 'testrealm'
973
974 auth = self.headers.getheader('authorization')
975 pairs = {}
976 try:
977 if not auth:
978 raise Exception('no auth')
979 if not auth.startswith('Digest'):
980 raise Exception('not digest')
981 # Pull out all the name="value" pairs as a dictionary.
982 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
983
984 # Make sure it's all valid.
985 if pairs['nonce'] != nonce:
986 raise Exception('wrong nonce')
987 if pairs['opaque'] != opaque:
988 raise Exception('wrong opaque')
989
990 # Check the 'response' value and make sure it matches our magic hash.
991 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000992 hash_a1 = _new_md5(
993 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000994 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000995 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000996 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000997 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
998 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000999 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001000
1001 if pairs['response'] != response:
1002 raise Exception('wrong password')
1003 except Exception, e:
1004 # Authentication failed.
1005 self.send_response(401)
1006 hdr = ('Digest '
1007 'realm="%s", '
1008 'domain="/", '
1009 'qop="auth", '
1010 'algorithm=MD5, '
1011 'nonce="%s", '
1012 'opaque="%s"') % (realm, nonce, opaque)
1013 if stale:
1014 hdr += ', stale="TRUE"'
1015 self.send_header('WWW-Authenticate', hdr)
1016 self.send_header('Content-type', 'text/html')
1017 self.end_headers()
1018 self.wfile.write('<html><head>')
1019 self.wfile.write('<title>Denied: %s</title>' % e)
1020 self.wfile.write('</head><body>')
1021 self.wfile.write('auth=%s<p>' % auth)
1022 self.wfile.write('pairs=%s<p>' % pairs)
1023 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1024 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1025 self.wfile.write('</body></html>')
1026 return True
1027
1028 # Authentication successful.
1029 self.send_response(200)
1030 self.send_header('Content-type', 'text/html')
1031 self.end_headers()
1032 self.wfile.write('<html><head>')
1033 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1034 self.wfile.write('</head><body>')
1035 self.wfile.write('auth=%s<p>' % auth)
1036 self.wfile.write('pairs=%s<p>' % pairs)
1037 self.wfile.write('</body></html>')
1038
1039 return True
1040
1041 def SlowServerHandler(self):
1042 """Wait for the user suggested time before responding. The syntax is
1043 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001044 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001045 return False
1046 query_char = self.path.find('?')
1047 wait_sec = 1.0
1048 if query_char >= 0:
1049 try:
1050 wait_sec = int(self.path[query_char + 1:])
1051 except ValueError:
1052 pass
1053 time.sleep(wait_sec)
1054 self.send_response(200)
1055 self.send_header('Content-type', 'text/plain')
1056 self.end_headers()
1057 self.wfile.write("waited %d seconds" % wait_sec)
1058 return True
1059
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001060 def ChunkedServerHandler(self):
1061 """Send chunked response. Allows to specify chunks parameters:
1062 - waitBeforeHeaders - ms to wait before sending headers
1063 - waitBetweenChunks - ms to wait between chunks
1064 - chunkSize - size of each chunk in bytes
1065 - chunksNumber - number of chunks
1066 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1067 waits one second, then sends headers and five chunks five bytes each."""
1068 if not self._ShouldHandleRequest("/chunked"):
1069 return False
1070 query_char = self.path.find('?')
1071 chunkedSettings = {'waitBeforeHeaders' : 0,
1072 'waitBetweenChunks' : 0,
1073 'chunkSize' : 5,
1074 'chunksNumber' : 5}
1075 if query_char >= 0:
1076 params = self.path[query_char + 1:].split('&')
1077 for param in params:
1078 keyValue = param.split('=')
1079 if len(keyValue) == 2:
1080 try:
1081 chunkedSettings[keyValue[0]] = int(keyValue[1])
1082 except ValueError:
1083 pass
1084 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1085 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1086 self.send_response(200)
1087 self.send_header('Content-type', 'text/plain')
1088 self.send_header('Connection', 'close')
1089 self.send_header('Transfer-Encoding', 'chunked')
1090 self.end_headers()
1091 # Chunked encoding: sending all chunks, then final zero-length chunk and
1092 # then final CRLF.
1093 for i in range(0, chunkedSettings['chunksNumber']):
1094 if i > 0:
1095 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1096 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1097 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1098 self.sendChunkHelp('')
1099 return True
1100
initial.commit94958cf2008-07-26 22:42:52 +00001101 def ContentTypeHandler(self):
1102 """Returns a string of html with the given content type. E.g.,
1103 /contenttype?text/css returns an html file with the Content-Type
1104 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001105 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001106 return False
1107 query_char = self.path.find('?')
1108 content_type = self.path[query_char + 1:].strip()
1109 if not content_type:
1110 content_type = 'text/html'
1111 self.send_response(200)
1112 self.send_header('Content-Type', content_type)
1113 self.end_headers()
1114 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1115 return True
1116
creis@google.com2f4f6a42011-03-25 19:44:19 +00001117 def NoContentHandler(self):
1118 """Returns a 204 No Content response."""
1119 if not self._ShouldHandleRequest("/nocontent"):
1120 return False
1121 self.send_response(204)
1122 self.end_headers()
1123 return True
1124
initial.commit94958cf2008-07-26 22:42:52 +00001125 def ServerRedirectHandler(self):
1126 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001127 '/server-redirect?http://foo.bar/asdf' to redirect to
1128 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001129
1130 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001131 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001132 return False
1133
1134 query_char = self.path.find('?')
1135 if query_char < 0 or len(self.path) <= query_char + 1:
1136 self.sendRedirectHelp(test_name)
1137 return True
1138 dest = self.path[query_char + 1:]
1139
1140 self.send_response(301) # moved permanently
1141 self.send_header('Location', dest)
1142 self.send_header('Content-type', 'text/html')
1143 self.end_headers()
1144 self.wfile.write('<html><head>')
1145 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1146
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001147 return True
initial.commit94958cf2008-07-26 22:42:52 +00001148
1149 def ClientRedirectHandler(self):
1150 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001151 '/client-redirect?http://foo.bar/asdf' to redirect to
1152 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001153
1154 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001155 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001156 return False
1157
1158 query_char = self.path.find('?');
1159 if query_char < 0 or len(self.path) <= query_char + 1:
1160 self.sendRedirectHelp(test_name)
1161 return True
1162 dest = self.path[query_char + 1:]
1163
1164 self.send_response(200)
1165 self.send_header('Content-type', 'text/html')
1166 self.end_headers()
1167 self.wfile.write('<html><head>')
1168 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1169 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1170
1171 return True
1172
tony@chromium.org03266982010-03-05 03:18:42 +00001173 def MultipartHandler(self):
1174 """Send a multipart response (10 text/html pages)."""
1175 test_name = "/multipart"
1176 if not self._ShouldHandleRequest(test_name):
1177 return False
1178
1179 num_frames = 10
1180 bound = '12345'
1181 self.send_response(200)
1182 self.send_header('Content-type',
1183 'multipart/x-mixed-replace;boundary=' + bound)
1184 self.end_headers()
1185
1186 for i in xrange(num_frames):
1187 self.wfile.write('--' + bound + '\r\n')
1188 self.wfile.write('Content-type: text/html\r\n\r\n')
1189 self.wfile.write('<title>page ' + str(i) + '</title>')
1190 self.wfile.write('page ' + str(i))
1191
1192 self.wfile.write('--' + bound + '--')
1193 return True
1194
initial.commit94958cf2008-07-26 22:42:52 +00001195 def DefaultResponseHandler(self):
1196 """This is the catch-all response handler for requests that aren't handled
1197 by one of the special handlers above.
1198 Note that we specify the content-length as without it the https connection
1199 is not closed properly (and the browser keeps expecting data)."""
1200
1201 contents = "Default response given for path: " + self.path
1202 self.send_response(200)
1203 self.send_header('Content-type', 'text/html')
1204 self.send_header("Content-Length", len(contents))
1205 self.end_headers()
1206 self.wfile.write(contents)
1207 return True
1208
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001209 def RedirectConnectHandler(self):
1210 """Sends a redirect to the CONNECT request for www.redirect.com. This
1211 response is not specified by the RFC, so the browser should not follow
1212 the redirect."""
1213
1214 if (self.path.find("www.redirect.com") < 0):
1215 return False
1216
1217 dest = "http://www.destination.com/foo.js"
1218
1219 self.send_response(302) # moved temporarily
1220 self.send_header('Location', dest)
1221 self.send_header('Connection', 'close')
1222 self.end_headers()
1223 return True
1224
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001225 def ServerAuthConnectHandler(self):
1226 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1227 response doesn't make sense because the proxy server cannot request
1228 server authentication."""
1229
1230 if (self.path.find("www.server-auth.com") < 0):
1231 return False
1232
1233 challenge = 'Basic realm="WallyWorld"'
1234
1235 self.send_response(401) # unauthorized
1236 self.send_header('WWW-Authenticate', challenge)
1237 self.send_header('Connection', 'close')
1238 self.end_headers()
1239 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001240
1241 def DefaultConnectResponseHandler(self):
1242 """This is the catch-all response handler for CONNECT requests that aren't
1243 handled by one of the special handlers above. Real Web servers respond
1244 with 400 to CONNECT requests."""
1245
1246 contents = "Your client has issued a malformed or illegal request."
1247 self.send_response(400) # bad request
1248 self.send_header('Content-type', 'text/html')
1249 self.send_header("Content-Length", len(contents))
1250 self.end_headers()
1251 self.wfile.write(contents)
1252 return True
1253
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001254 def DeviceManagementHandler(self):
1255 """Delegates to the device management service used for cloud policy."""
1256 if not self._ShouldHandleRequest("/device_management"):
1257 return False
1258
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001259 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001260
1261 if not self.server._device_management_handler:
1262 import device_management
1263 policy_path = os.path.join(self.server.data_dir, 'device_management')
1264 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001265 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001266 self.server.policy_keys,
1267 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001268
1269 http_response, raw_reply = (
1270 self.server._device_management_handler.HandleRequest(self.path,
1271 self.headers,
1272 raw_request))
1273 self.send_response(http_response)
1274 self.end_headers()
1275 self.wfile.write(raw_reply)
1276 return True
1277
initial.commit94958cf2008-07-26 22:42:52 +00001278 # called by the redirect handling function when there is no parameter
1279 def sendRedirectHelp(self, redirect_name):
1280 self.send_response(200)
1281 self.send_header('Content-type', 'text/html')
1282 self.end_headers()
1283 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1284 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1285 self.wfile.write('</body></html>')
1286
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001287 # called by chunked handling function
1288 def sendChunkHelp(self, chunk):
1289 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1290 self.wfile.write('%X\r\n' % len(chunk))
1291 self.wfile.write(chunk)
1292 self.wfile.write('\r\n')
1293
akalin@chromium.org154bb132010-11-12 02:20:27 +00001294
1295class SyncPageHandler(BasePageHandler):
1296 """Handler for the main HTTP sync server."""
1297
1298 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001299 get_handlers = [self.ChromiumSyncMigrationOpHandler,
1300 self.ChromiumSyncTimeHandler]
1301 post_handlers = [self.ChromiumSyncCommandHandler,
1302 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001303 BasePageHandler.__init__(self, request, client_address,
1304 sync_http_server, [], get_handlers,
1305 post_handlers, [])
1306
1307 def ChromiumSyncTimeHandler(self):
1308 """Handle Chromium sync .../time requests.
1309
1310 The syncer sometimes checks server reachability by examining /time.
1311 """
1312 test_name = "/chromiumsync/time"
1313 if not self._ShouldHandleRequest(test_name):
1314 return False
1315
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001316 # Chrome hates it if we send a response before reading the request.
1317 if self.headers.getheader('content-length'):
1318 length = int(self.headers.getheader('content-length'))
1319 raw_request = self.rfile.read(length)
1320
akalin@chromium.org154bb132010-11-12 02:20:27 +00001321 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001322 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001323 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001324 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001325 return True
1326
1327 def ChromiumSyncCommandHandler(self):
1328 """Handle a chromiumsync command arriving via http.
1329
1330 This covers all sync protocol commands: authentication, getupdates, and
1331 commit.
1332 """
1333 test_name = "/chromiumsync/command"
1334 if not self._ShouldHandleRequest(test_name):
1335 return False
1336
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001337 length = int(self.headers.getheader('content-length'))
1338 raw_request = self.rfile.read(length)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001339
1340 http_response, raw_reply = self.server.HandleCommand(
1341 self.path, raw_request)
1342 self.send_response(http_response)
1343 self.end_headers()
1344 self.wfile.write(raw_reply)
1345 return True
1346
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001347 def ChromiumSyncMigrationOpHandler(self):
1348 """Handle a chromiumsync test-op command arriving via http.
1349 """
1350 test_name = "/chromiumsync/migrate"
1351 if not self._ShouldHandleRequest(test_name):
1352 return False
1353
1354 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1355 self.path)
1356 self.send_response(http_response)
1357 self.send_header('Content-Type', 'text/html')
1358 self.send_header('Content-Length', len(raw_reply))
1359 self.end_headers()
1360 self.wfile.write(raw_reply)
1361 return True
1362
akalin@chromium.org154bb132010-11-12 02:20:27 +00001363
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001364def MakeDataDir():
1365 if options.data_dir:
1366 if not os.path.isdir(options.data_dir):
1367 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1368 return None
1369 my_data_dir = options.data_dir
1370 else:
1371 # Create the default path to our data dir, relative to the exe dir.
1372 my_data_dir = os.path.dirname(sys.argv[0])
1373 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001374 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001375
1376 #TODO(ibrar): Must use Find* funtion defined in google\tools
1377 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1378
1379 return my_data_dir
1380
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001381class FileMultiplexer:
1382 def __init__(self, fd1, fd2) :
1383 self.__fd1 = fd1
1384 self.__fd2 = fd2
1385
1386 def __del__(self) :
1387 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1388 self.__fd1.close()
1389 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1390 self.__fd2.close()
1391
1392 def write(self, text) :
1393 self.__fd1.write(text)
1394 self.__fd2.write(text)
1395
1396 def flush(self) :
1397 self.__fd1.flush()
1398 self.__fd2.flush()
1399
initial.commit94958cf2008-07-26 22:42:52 +00001400def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001401 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001402 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001403 if options.log_to_console:
1404 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1405 else:
1406 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001407
1408 port = options.port
1409
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001410 server_data = {}
1411
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001412 if options.server_type == SERVER_HTTP:
1413 if options.cert:
1414 # let's make sure the cert file exists.
1415 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001416 print 'specified server cert file not found: ' + options.cert + \
1417 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001418 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001419 for ca_cert in options.ssl_client_ca:
1420 if not os.path.isfile(ca_cert):
1421 print 'specified trusted client CA file not found: ' + ca_cert + \
1422 ' exiting...'
1423 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001424 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001425 options.ssl_client_auth, options.ssl_client_ca,
1426 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001427 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001428 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001429 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001430 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001431
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001432 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001433 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001434 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001435 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001436 server.policy_keys = options.policy_keys
1437 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001438 elif options.server_type == SERVER_SYNC:
1439 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1440 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001441 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1442 server_data['port'] = server.server_port
1443 server_data['xmpp_port'] = server.xmpp_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001444 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001445 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001446 my_data_dir = MakeDataDir()
1447
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001448 # Instantiate a dummy authorizer for managing 'virtual' users
1449 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1450
1451 # Define a new user having full r/w permissions and a read-only
1452 # anonymous user
1453 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1454
1455 authorizer.add_anonymous(my_data_dir)
1456
1457 # Instantiate FTP handler class
1458 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1459 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001460
1461 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001462 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1463 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001464
1465 # Instantiate FTP server class and listen to 127.0.0.1:port
1466 address = ('127.0.0.1', port)
1467 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001468 server_data['port'] = server.socket.getsockname()[1]
1469 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001470
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001471 # Notify the parent that we've started. (BaseServer subclasses
1472 # bind their sockets on construction.)
1473 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001474 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001475 server_data_len = len(server_data_json)
1476 print 'sending server_data: %s (%d bytes)' % (
1477 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001478 if sys.platform == 'win32':
1479 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1480 else:
1481 fd = options.startup_pipe
1482 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001483 # First write the data length as an unsigned 4-byte value. This
1484 # is _not_ using network byte ordering since the other end of the
1485 # pipe is on the same machine.
1486 startup_pipe.write(struct.pack('=L', server_data_len))
1487 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001488 startup_pipe.close()
1489
initial.commit94958cf2008-07-26 22:42:52 +00001490 try:
1491 server.serve_forever()
1492 except KeyboardInterrupt:
1493 print 'shutting down server'
1494 server.stop = True
1495
1496if __name__ == '__main__':
1497 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001498 option_parser.add_option("-f", '--ftp', action='store_const',
1499 const=SERVER_FTP, default=SERVER_HTTP,
1500 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001501 help='start up an FTP server.')
1502 option_parser.add_option('', '--sync', action='store_const',
1503 const=SERVER_SYNC, default=SERVER_HTTP,
1504 dest='server_type',
1505 help='start up a sync server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001506 option_parser.add_option('', '--log-to-console', action='store_const',
1507 const=True, default=False,
1508 dest='log_to_console',
1509 help='Enables or disables sys.stdout logging to '
1510 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001511 option_parser.add_option('', '--port', default='0', type='int',
1512 help='Port used by the server. If unspecified, the '
1513 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001514 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001515 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001516 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001517 help='Specify that https should be used, specify '
1518 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001519 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001520 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1521 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001522 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1523 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001524 'should include the CA named in the subject of '
1525 'the DER-encoded certificate contained in the '
1526 'specified file. This option may appear multiple '
1527 'times, indicating multiple CA names should be '
1528 'sent in the request.')
1529 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1530 help='Specify the bulk encryption algorithm(s)'
1531 'that will be accepted by the SSL server. Valid '
1532 'values are "aes256", "aes128", "3des", "rc4". If '
1533 'omitted, all algorithms will be used. This '
1534 'option may appear multiple times, indicating '
1535 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001536 option_parser.add_option('', '--file-root-url', default='/files/',
1537 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001538 option_parser.add_option('', '--startup-pipe', type='int',
1539 dest='startup_pipe',
1540 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001541 option_parser.add_option('', '--policy-key', action='append',
1542 dest='policy_keys',
1543 help='Specify a path to a PEM-encoded private key '
1544 'to use for policy signing. May be specified '
1545 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001546 'the server. If ther server has multiple keys, it '
1547 'will rotate through them in at each request a '
1548 'round-robin fashion. The server will generate a '
1549 'random key if none is specified on the command '
1550 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001551 option_parser.add_option('', '--policy-user', default='user@example.com',
1552 dest='policy_user',
1553 help='Specify the user name the server should '
1554 'report back to the client as the user owning the '
1555 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001556 options, args = option_parser.parse_args()
1557
1558 sys.exit(main(options, args))