blob: c54d42504347638f2bbab7d717573502c54d760c [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00002# Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
6"""This is a simple HTTP server used for testing Chrome.
7
8It supports several test URLs, as specified by the handlers in TestPageHandler.
nick@chromium.org8daefd62010-10-27 23:24:05 +00009It defaults to living on localhost:8888.
initial.commit94958cf2008-07-26 22:42:52 +000010It can use https if you specify the flag --https=CERT where CERT is the path
11to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000012"""
13
14import base64
15import BaseHTTPServer
16import cgi
initial.commit94958cf2008-07-26 22:42:52 +000017import optparse
18import os
19import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000020import shutil
initial.commit94958cf2008-07-26 22:42:52 +000021import SocketServer
22import sys
23import time
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000024import urllib2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000025import warnings
26
27# Ignore deprecation warnings, they make our output more cluttered.
28warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000029
30import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000031import tlslite
32import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000033
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000034try:
35 import hashlib
36 _new_md5 = hashlib.md5
37except ImportError:
38 import md5
39 _new_md5 = md5.new
40
davidben@chromium.org06fcf202010-09-22 18:15:23 +000041if sys.platform == 'win32':
42 import msvcrt
43
maruel@chromium.org756cf982009-03-05 12:46:38 +000044SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000045SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000046
47debug_output = sys.stderr
48def debug(str):
49 debug_output.write(str + "\n")
50 debug_output.flush()
51
52class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
53 """This is a specialization of of BaseHTTPServer to allow it
54 to be exited cleanly (by setting its "stop" member to True)."""
55
56 def serve_forever(self):
57 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000058 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000059 while not self.stop:
60 self.handle_request()
61 self.socket.close()
62
63class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
64 """This is a specialization of StoppableHTTPerver that add https support."""
65
davidben@chromium.org31282a12010-08-07 01:10:02 +000066 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000067 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000068 s = open(cert_path).read()
69 x509 = tlslite.api.X509()
70 x509.parse(s)
71 self.cert_chain = tlslite.api.X509CertChain([x509])
72 s = open(cert_path).read()
73 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000074 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000075 self.ssl_client_cas = []
76 for ca_file in ssl_client_cas:
77 s = open(ca_file).read()
78 x509 = tlslite.api.X509()
79 x509.parse(s)
80 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000081 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
82 if ssl_bulk_ciphers is not None:
83 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000084
85 self.session_cache = tlslite.api.SessionCache()
86 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
87
88 def handshake(self, tlsConnection):
89 """Creates the SSL connection."""
90 try:
91 tlsConnection.handshakeServer(certChain=self.cert_chain,
92 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000093 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000094 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000095 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000096 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +000097 tlsConnection.ignoreAbruptClose = True
98 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000099 except tlslite.api.TLSAbruptCloseError:
100 # Ignore abrupt close.
101 return True
initial.commit94958cf2008-07-26 22:42:52 +0000102 except tlslite.api.TLSError, error:
103 print "Handshake failure:", str(error)
104 return False
105
106class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
107
108 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000109 self._connect_handlers = [
110 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000111 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000112 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000113 self._get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000114 self.NoCacheMaxAgeTimeHandler,
115 self.NoCacheTimeHandler,
116 self.CacheTimeHandler,
117 self.CacheExpiresHandler,
118 self.CacheProxyRevalidateHandler,
119 self.CachePrivateHandler,
120 self.CachePublicHandler,
121 self.CacheSMaxAgeHandler,
122 self.CacheMustRevalidateHandler,
123 self.CacheMustRevalidateMaxAgeHandler,
124 self.CacheNoStoreHandler,
125 self.CacheNoStoreMaxAgeHandler,
126 self.CacheNoTransformHandler,
127 self.DownloadHandler,
128 self.DownloadFinishHandler,
129 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000130 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000131 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000132 self.FileHandler,
133 self.RealFileWithCommonHeaderHandler,
134 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000135 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000136 self.AuthBasicHandler,
137 self.AuthDigestHandler,
138 self.SlowServerHandler,
139 self.ContentTypeHandler,
140 self.ServerRedirectHandler,
141 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000142 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000143 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000144 self.DefaultResponseHandler]
145 self._post_handlers = [
146 self.EchoTitleHandler,
147 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000148 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000149 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000150 self._put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000151 self.EchoTitleHandler,
152 self.EchoAllHandler,
153 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000154
maruel@google.come250a9b2009-03-10 17:39:46 +0000155 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000156 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000157 'gif': 'image/gif',
158 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000159 'jpg' : 'image/jpeg',
160 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000161 }
initial.commit94958cf2008-07-26 22:42:52 +0000162 self._default_mime_type = 'text/html'
163
maruel@google.come250a9b2009-03-10 17:39:46 +0000164 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
165 client_address,
166 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000167
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000168 def log_request(self, *args, **kwargs):
169 # Disable request logging to declutter test log output.
170 pass
171
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000172 def _ShouldHandleRequest(self, handler_name):
173 """Determines if the path can be handled by the handler.
174
175 We consider a handler valid if the path begins with the
176 handler name. It can optionally be followed by "?*", "/*".
177 """
178
179 pattern = re.compile('%s($|\?|/).*' % handler_name)
180 return pattern.match(self.path)
181
initial.commit94958cf2008-07-26 22:42:52 +0000182 def GetMIMETypeFromName(self, file_name):
183 """Returns the mime type for the specified file_name. So far it only looks
184 at the file extension."""
185
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000186 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000187 if len(extension) == 0:
188 # no extension.
189 return self._default_mime_type
190
ericroman@google.comc17ca532009-05-07 03:51:05 +0000191 # extension starts with a dot, so we need to remove it
192 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000193
initial.commit94958cf2008-07-26 22:42:52 +0000194 def NoCacheMaxAgeTimeHandler(self):
195 """This request handler yields a page with the title set to the current
196 system time, and no caching requested."""
197
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000198 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000199 return False
200
201 self.send_response(200)
202 self.send_header('Cache-Control', 'max-age=0')
203 self.send_header('Content-type', 'text/html')
204 self.end_headers()
205
maruel@google.come250a9b2009-03-10 17:39:46 +0000206 self.wfile.write('<html><head><title>%s</title></head></html>' %
207 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000208
209 return True
210
211 def NoCacheTimeHandler(self):
212 """This request handler yields a page with the title set to the current
213 system time, and no caching requested."""
214
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000215 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000216 return False
217
218 self.send_response(200)
219 self.send_header('Cache-Control', 'no-cache')
220 self.send_header('Content-type', 'text/html')
221 self.end_headers()
222
maruel@google.come250a9b2009-03-10 17:39:46 +0000223 self.wfile.write('<html><head><title>%s</title></head></html>' %
224 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000225
226 return True
227
228 def CacheTimeHandler(self):
229 """This request handler yields a page with the title set to the current
230 system time, and allows caching for one minute."""
231
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000232 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000233 return False
234
235 self.send_response(200)
236 self.send_header('Cache-Control', 'max-age=60')
237 self.send_header('Content-type', 'text/html')
238 self.end_headers()
239
maruel@google.come250a9b2009-03-10 17:39:46 +0000240 self.wfile.write('<html><head><title>%s</title></head></html>' %
241 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000242
243 return True
244
245 def CacheExpiresHandler(self):
246 """This request handler yields a page with the title set to the current
247 system time, and set the page to expire on 1 Jan 2099."""
248
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000249 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000250 return False
251
252 self.send_response(200)
253 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
254 self.send_header('Content-type', 'text/html')
255 self.end_headers()
256
maruel@google.come250a9b2009-03-10 17:39:46 +0000257 self.wfile.write('<html><head><title>%s</title></head></html>' %
258 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000259
260 return True
261
262 def CacheProxyRevalidateHandler(self):
263 """This request handler yields a page with the title set to the current
264 system time, and allows caching for 60 seconds"""
265
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000266 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000267 return False
268
269 self.send_response(200)
270 self.send_header('Content-type', 'text/html')
271 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
272 self.end_headers()
273
maruel@google.come250a9b2009-03-10 17:39:46 +0000274 self.wfile.write('<html><head><title>%s</title></head></html>' %
275 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000276
277 return True
278
279 def CachePrivateHandler(self):
280 """This request handler yields a page with the title set to the current
281 system time, and allows caching for 5 seconds."""
282
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000283 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000284 return False
285
286 self.send_response(200)
287 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000288 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000289 self.end_headers()
290
maruel@google.come250a9b2009-03-10 17:39:46 +0000291 self.wfile.write('<html><head><title>%s</title></head></html>' %
292 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000293
294 return True
295
296 def CachePublicHandler(self):
297 """This request handler yields a page with the title set to the current
298 system time, and allows caching for 5 seconds."""
299
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000300 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000301 return False
302
303 self.send_response(200)
304 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000305 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000306 self.end_headers()
307
maruel@google.come250a9b2009-03-10 17:39:46 +0000308 self.wfile.write('<html><head><title>%s</title></head></html>' %
309 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000310
311 return True
312
313 def CacheSMaxAgeHandler(self):
314 """This request handler yields a page with the title set to the current
315 system time, and does not allow for caching."""
316
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000317 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000318 return False
319
320 self.send_response(200)
321 self.send_header('Content-type', 'text/html')
322 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
323 self.end_headers()
324
maruel@google.come250a9b2009-03-10 17:39:46 +0000325 self.wfile.write('<html><head><title>%s</title></head></html>' %
326 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000327
328 return True
329
330 def CacheMustRevalidateHandler(self):
331 """This request handler yields a page with the title set to the current
332 system time, and does not allow caching."""
333
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000334 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000335 return False
336
337 self.send_response(200)
338 self.send_header('Content-type', 'text/html')
339 self.send_header('Cache-Control', 'must-revalidate')
340 self.end_headers()
341
maruel@google.come250a9b2009-03-10 17:39:46 +0000342 self.wfile.write('<html><head><title>%s</title></head></html>' %
343 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000344
345 return True
346
347 def CacheMustRevalidateMaxAgeHandler(self):
348 """This request handler yields a page with the title set to the current
349 system time, and does not allow caching event though max-age of 60
350 seconds is specified."""
351
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000352 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000353 return False
354
355 self.send_response(200)
356 self.send_header('Content-type', 'text/html')
357 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
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
initial.commit94958cf2008-07-26 22:42:52 +0000365 def CacheNoStoreHandler(self):
366 """This request handler yields a page with the title set to the current
367 system time, and does not allow the page to be stored."""
368
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000369 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000370 return False
371
372 self.send_response(200)
373 self.send_header('Content-type', 'text/html')
374 self.send_header('Cache-Control', 'no-store')
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 CacheNoStoreMaxAgeHandler(self):
383 """This request handler yields a page with the title set to the current
384 system time, and does not allow the page to be stored even though max-age
385 of 60 seconds is specified."""
386
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000387 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000388 return False
389
390 self.send_response(200)
391 self.send_header('Content-type', 'text/html')
392 self.send_header('Cache-Control', 'max-age=60, no-store')
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
401 def CacheNoTransformHandler(self):
402 """This request handler yields a page with the title set to the current
403 system time, and does not allow the content to transformed during
404 user-agent caching"""
405
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000406 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000407 return False
408
409 self.send_response(200)
410 self.send_header('Content-type', 'text/html')
411 self.send_header('Cache-Control', 'no-transform')
412 self.end_headers()
413
maruel@google.come250a9b2009-03-10 17:39:46 +0000414 self.wfile.write('<html><head><title>%s</title></head></html>' %
415 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000416
417 return True
418
419 def EchoHeader(self):
420 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000421 """The only difference between this function and the EchoHeaderOverride"""
422 """function is in the parameter being passed to the helper function"""
423 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000424
ananta@chromium.org219b2062009-10-23 16:09:41 +0000425 def EchoHeaderOverride(self):
426 """This handler echoes back the value of a specific request header."""
427 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
428 """IE to issue HTTP requests using the host network stack."""
429 """The Accept and Charset tests which expect the server to echo back"""
430 """the corresponding headers fail here as IE returns cached responses"""
431 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
432 """treats this request as a new request and does not cache it."""
433 return self.EchoHeaderHelper("/echoheaderoverride")
434
435 def EchoHeaderHelper(self, echo_header):
436 """This function echoes back the value of the request header passed in."""
437 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000438 return False
439
440 query_char = self.path.find('?')
441 if query_char != -1:
442 header_name = self.path[query_char+1:]
443
444 self.send_response(200)
445 self.send_header('Content-type', 'text/plain')
446 self.send_header('Cache-control', 'max-age=60000')
447 # insert a vary header to properly indicate that the cachability of this
448 # request is subject to value of the request header being echoed.
449 if len(header_name) > 0:
450 self.send_header('Vary', header_name)
451 self.end_headers()
452
453 if len(header_name) > 0:
454 self.wfile.write(self.headers.getheader(header_name))
455
456 return True
457
458 def EchoHandler(self):
459 """This handler just echoes back the payload of the request, for testing
460 form submission."""
461
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000462 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000463 return False
464
465 self.send_response(200)
466 self.send_header('Content-type', 'text/html')
467 self.end_headers()
468 length = int(self.headers.getheader('content-length'))
469 request = self.rfile.read(length)
470 self.wfile.write(request)
471 return True
472
473 def EchoTitleHandler(self):
474 """This handler is like Echo, but sets the page title to the request."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
480 self.send_header('Content-type', 'text/html')
481 self.end_headers()
482 length = int(self.headers.getheader('content-length'))
483 request = self.rfile.read(length)
484 self.wfile.write('<html><head><title>')
485 self.wfile.write(request)
486 self.wfile.write('</title></head></html>')
487 return True
488
489 def EchoAllHandler(self):
490 """This handler yields a (more) human-readable page listing information
491 about the request header & contents."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
497 self.send_header('Content-type', 'text/html')
498 self.end_headers()
499 self.wfile.write('<html><head><style>'
500 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
501 '</style></head><body>'
502 '<div style="float: right">'
nick@chromium.org8daefd62010-10-27 23:24:05 +0000503 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000504 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000505
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000506 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000507 length = int(self.headers.getheader('content-length'))
508 qs = self.rfile.read(length)
509 params = cgi.parse_qs(qs, keep_blank_values=1)
510
511 for param in params:
512 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 self.wfile.write('</pre>')
515
516 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
517
518 self.wfile.write('</body></html>')
519 return True
520
521 def DownloadHandler(self):
522 """This handler sends a downloadable file with or without reporting
523 the size (6K)."""
524
525 if self.path.startswith("/download-unknown-size"):
526 send_length = False
527 elif self.path.startswith("/download-known-size"):
528 send_length = True
529 else:
530 return False
531
532 #
533 # The test which uses this functionality is attempting to send
534 # small chunks of data to the client. Use a fairly large buffer
535 # so that we'll fill chrome's IO buffer enough to force it to
536 # actually write the data.
537 # See also the comments in the client-side of this test in
538 # download_uitest.cc
539 #
540 size_chunk1 = 35*1024
541 size_chunk2 = 10*1024
542
543 self.send_response(200)
544 self.send_header('Content-type', 'application/octet-stream')
545 self.send_header('Cache-Control', 'max-age=0')
546 if send_length:
547 self.send_header('Content-Length', size_chunk1 + size_chunk2)
548 self.end_headers()
549
550 # First chunk of data:
551 self.wfile.write("*" * size_chunk1)
552 self.wfile.flush()
553
554 # handle requests until one of them clears this flag.
555 self.server.waitForDownload = True
556 while self.server.waitForDownload:
557 self.server.handle_request()
558
559 # Second chunk of data:
560 self.wfile.write("*" * size_chunk2)
561 return True
562
563 def DownloadFinishHandler(self):
564 """This handler just tells the server to finish the current download."""
565
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000566 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000567 return False
568
569 self.server.waitForDownload = False
570 self.send_response(200)
571 self.send_header('Content-type', 'text/html')
572 self.send_header('Cache-Control', 'max-age=0')
573 self.end_headers()
574 return True
575
576 def FileHandler(self):
577 """This handler sends the contents of the requested file. Wow, it's like
578 a real webserver!"""
579
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000580 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000581 if not self.path.startswith(prefix):
582 return False
583
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000584 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000585 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000586 self.rfile.read(int(self.headers.getheader('content-length')))
587
initial.commit94958cf2008-07-26 22:42:52 +0000588 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000589 if file.find('?') > -1:
590 # Ignore the query parameters entirely.
591 url, querystring = file.split('?')
592 else:
593 url = file
594 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000595 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000596 if os.path.isdir(path):
597 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000598
599 if not os.path.isfile(path):
600 print "File not found " + file + " full path:" + path
601 self.send_error(404)
602 return True
603
604 f = open(path, "rb")
605 data = f.read()
606 f.close()
607
608 # If file.mock-http-headers exists, it contains the headers we
609 # should send. Read them in and parse them.
610 headers_path = path + '.mock-http-headers'
611 if os.path.isfile(headers_path):
612 f = open(headers_path, "r")
613
614 # "HTTP/1.1 200 OK"
615 response = f.readline()
616 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
617 self.send_response(int(status_code))
618
619 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000620 header_values = re.findall('(\S+):\s*(.*)', line)
621 if len(header_values) > 0:
622 # "name: value"
623 name, value = header_values[0]
624 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000625 f.close()
626 else:
627 # Could be more generic once we support mime-type sniffing, but for
628 # now we need to set it explicitly.
629 self.send_response(200)
630 self.send_header('Content-type', self.GetMIMETypeFromName(file))
631 self.send_header('Content-Length', len(data))
632 self.end_headers()
633
634 self.wfile.write(data)
635
636 return True
637
638 def RealFileWithCommonHeaderHandler(self):
639 """This handler sends the contents of the requested file without the pseudo
640 http head!"""
641
642 prefix='/realfiles/'
643 if not self.path.startswith(prefix):
644 return False
645
646 file = self.path[len(prefix):]
647 path = os.path.join(self.server.data_dir, file)
648
649 try:
650 f = open(path, "rb")
651 data = f.read()
652 f.close()
653
654 # just simply set the MIME as octal stream
655 self.send_response(200)
656 self.send_header('Content-type', 'application/octet-stream')
657 self.end_headers()
658
659 self.wfile.write(data)
660 except:
661 self.send_error(404)
662
663 return True
664
665 def RealBZ2FileWithCommonHeaderHandler(self):
666 """This handler sends the bzip2 contents of the requested file with
667 corresponding Content-Encoding field in http head!"""
668
669 prefix='/realbz2files/'
670 if not self.path.startswith(prefix):
671 return False
672
673 parts = self.path.split('?')
674 file = parts[0][len(prefix):]
675 path = os.path.join(self.server.data_dir, file) + '.bz2'
676
677 if len(parts) > 1:
678 options = parts[1]
679 else:
680 options = ''
681
682 try:
683 self.send_response(200)
684 accept_encoding = self.headers.get("Accept-Encoding")
685 if accept_encoding.find("bzip2") != -1:
686 f = open(path, "rb")
687 data = f.read()
688 f.close()
689 self.send_header('Content-Encoding', 'bzip2')
690 self.send_header('Content-type', 'application/x-bzip2')
691 self.end_headers()
692 if options == 'incremental-header':
693 self.wfile.write(data[:1])
694 self.wfile.flush()
695 time.sleep(1.0)
696 self.wfile.write(data[1:])
697 else:
698 self.wfile.write(data)
699 else:
700 """client do not support bzip2 format, send pseudo content
701 """
702 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
703 self.end_headers()
704 self.wfile.write("you do not support bzip2 encoding")
705 except:
706 self.send_error(404)
707
708 return True
709
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000710 def SetCookieHandler(self):
711 """This handler just sets a cookie, for testing cookie handling."""
712
713 if not self._ShouldHandleRequest("/set-cookie"):
714 return False
715
716 query_char = self.path.find('?')
717 if query_char != -1:
718 cookie_values = self.path[query_char + 1:].split('&')
719 else:
720 cookie_values = ("",)
721 self.send_response(200)
722 self.send_header('Content-type', 'text/html')
723 for cookie_value in cookie_values:
724 self.send_header('Set-Cookie', '%s' % cookie_value)
725 self.end_headers()
726 for cookie_value in cookie_values:
727 self.wfile.write('%s' % cookie_value)
728 return True
729
initial.commit94958cf2008-07-26 22:42:52 +0000730 def AuthBasicHandler(self):
731 """This handler tests 'Basic' authentication. It just sends a page with
732 title 'user/pass' if you succeed."""
733
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000734 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000735 return False
736
737 username = userpass = password = b64str = ""
738
ericroman@google.com239b4d82009-03-27 04:00:22 +0000739 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
740
initial.commit94958cf2008-07-26 22:42:52 +0000741 auth = self.headers.getheader('authorization')
742 try:
743 if not auth:
744 raise Exception('no auth')
745 b64str = re.findall(r'Basic (\S+)', auth)[0]
746 userpass = base64.b64decode(b64str)
747 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
748 if password != 'secret':
749 raise Exception('wrong password')
750 except Exception, e:
751 # Authentication failed.
752 self.send_response(401)
753 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
754 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000755 if set_cookie_if_challenged:
756 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000757 self.end_headers()
758 self.wfile.write('<html><head>')
759 self.wfile.write('<title>Denied: %s</title>' % e)
760 self.wfile.write('</head><body>')
761 self.wfile.write('auth=%s<p>' % auth)
762 self.wfile.write('b64str=%s<p>' % b64str)
763 self.wfile.write('username: %s<p>' % username)
764 self.wfile.write('userpass: %s<p>' % userpass)
765 self.wfile.write('password: %s<p>' % password)
766 self.wfile.write('You sent:<br>%s<p>' % self.headers)
767 self.wfile.write('</body></html>')
768 return True
769
770 # Authentication successful. (Return a cachable response to allow for
771 # testing cached pages that require authentication.)
772 if_none_match = self.headers.getheader('if-none-match')
773 if if_none_match == "abc":
774 self.send_response(304)
775 self.end_headers()
776 else:
777 self.send_response(200)
778 self.send_header('Content-type', 'text/html')
779 self.send_header('Cache-control', 'max-age=60000')
780 self.send_header('Etag', 'abc')
781 self.end_headers()
782 self.wfile.write('<html><head>')
783 self.wfile.write('<title>%s/%s</title>' % (username, password))
784 self.wfile.write('</head><body>')
785 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000786 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000787 self.wfile.write('</body></html>')
788
789 return True
790
tonyg@chromium.org75054202010-03-31 22:06:10 +0000791 def GetNonce(self, force_reset=False):
792 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000793
tonyg@chromium.org75054202010-03-31 22:06:10 +0000794 This is a fake implementation. A real implementation would only use a given
795 nonce a single time (hence the name n-once). However, for the purposes of
796 unittesting, we don't care about the security of the nonce.
797
798 Args:
799 force_reset: Iff set, the nonce will be changed. Useful for testing the
800 "stale" response.
801 """
802 if force_reset or not self.server.nonce_time:
803 self.server.nonce_time = time.time()
804 return _new_md5('privatekey%s%d' %
805 (self.path, self.server.nonce_time)).hexdigest()
806
807 def AuthDigestHandler(self):
808 """This handler tests 'Digest' authentication.
809
810 It just sends a page with title 'user/pass' if you succeed.
811
812 A stale response is sent iff "stale" is present in the request path.
813 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000814 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000815 return False
816
tonyg@chromium.org75054202010-03-31 22:06:10 +0000817 stale = 'stale' in self.path
818 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000819 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000820 password = 'secret'
821 realm = 'testrealm'
822
823 auth = self.headers.getheader('authorization')
824 pairs = {}
825 try:
826 if not auth:
827 raise Exception('no auth')
828 if not auth.startswith('Digest'):
829 raise Exception('not digest')
830 # Pull out all the name="value" pairs as a dictionary.
831 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
832
833 # Make sure it's all valid.
834 if pairs['nonce'] != nonce:
835 raise Exception('wrong nonce')
836 if pairs['opaque'] != opaque:
837 raise Exception('wrong opaque')
838
839 # Check the 'response' value and make sure it matches our magic hash.
840 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000841 hash_a1 = _new_md5(
842 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000843 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000844 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000845 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000846 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
847 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000848 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000849
850 if pairs['response'] != response:
851 raise Exception('wrong password')
852 except Exception, e:
853 # Authentication failed.
854 self.send_response(401)
855 hdr = ('Digest '
856 'realm="%s", '
857 'domain="/", '
858 'qop="auth", '
859 'algorithm=MD5, '
860 'nonce="%s", '
861 'opaque="%s"') % (realm, nonce, opaque)
862 if stale:
863 hdr += ', stale="TRUE"'
864 self.send_header('WWW-Authenticate', hdr)
865 self.send_header('Content-type', 'text/html')
866 self.end_headers()
867 self.wfile.write('<html><head>')
868 self.wfile.write('<title>Denied: %s</title>' % e)
869 self.wfile.write('</head><body>')
870 self.wfile.write('auth=%s<p>' % auth)
871 self.wfile.write('pairs=%s<p>' % pairs)
872 self.wfile.write('You sent:<br>%s<p>' % self.headers)
873 self.wfile.write('We are replying:<br>%s<p>' % hdr)
874 self.wfile.write('</body></html>')
875 return True
876
877 # Authentication successful.
878 self.send_response(200)
879 self.send_header('Content-type', 'text/html')
880 self.end_headers()
881 self.wfile.write('<html><head>')
882 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
883 self.wfile.write('</head><body>')
884 self.wfile.write('auth=%s<p>' % auth)
885 self.wfile.write('pairs=%s<p>' % pairs)
886 self.wfile.write('</body></html>')
887
888 return True
889
890 def SlowServerHandler(self):
891 """Wait for the user suggested time before responding. The syntax is
892 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000893 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000894 return False
895 query_char = self.path.find('?')
896 wait_sec = 1.0
897 if query_char >= 0:
898 try:
899 wait_sec = int(self.path[query_char + 1:])
900 except ValueError:
901 pass
902 time.sleep(wait_sec)
903 self.send_response(200)
904 self.send_header('Content-type', 'text/plain')
905 self.end_headers()
906 self.wfile.write("waited %d seconds" % wait_sec)
907 return True
908
909 def ContentTypeHandler(self):
910 """Returns a string of html with the given content type. E.g.,
911 /contenttype?text/css returns an html file with the Content-Type
912 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000913 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000914 return False
915 query_char = self.path.find('?')
916 content_type = self.path[query_char + 1:].strip()
917 if not content_type:
918 content_type = 'text/html'
919 self.send_response(200)
920 self.send_header('Content-Type', content_type)
921 self.end_headers()
922 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
923 return True
924
925 def ServerRedirectHandler(self):
926 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000927 '/server-redirect?http://foo.bar/asdf' to redirect to
928 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000929
930 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000931 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000932 return False
933
934 query_char = self.path.find('?')
935 if query_char < 0 or len(self.path) <= query_char + 1:
936 self.sendRedirectHelp(test_name)
937 return True
938 dest = self.path[query_char + 1:]
939
940 self.send_response(301) # moved permanently
941 self.send_header('Location', dest)
942 self.send_header('Content-type', 'text/html')
943 self.end_headers()
944 self.wfile.write('<html><head>')
945 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
946
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000947 return True
initial.commit94958cf2008-07-26 22:42:52 +0000948
949 def ClientRedirectHandler(self):
950 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000951 '/client-redirect?http://foo.bar/asdf' to redirect to
952 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000953
954 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000955 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000956 return False
957
958 query_char = self.path.find('?');
959 if query_char < 0 or len(self.path) <= query_char + 1:
960 self.sendRedirectHelp(test_name)
961 return True
962 dest = self.path[query_char + 1:]
963
964 self.send_response(200)
965 self.send_header('Content-type', 'text/html')
966 self.end_headers()
967 self.wfile.write('<html><head>')
968 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
969 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
970
971 return True
972
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000973 def ChromiumSyncTimeHandler(self):
974 """Handle Chromium sync .../time requests.
975
976 The syncer sometimes checks server reachability by examining /time.
977 """
978 test_name = "/chromiumsync/time"
979 if not self._ShouldHandleRequest(test_name):
980 return False
981
982 self.send_response(200)
983 self.send_header('Content-type', 'text/html')
984 self.end_headers()
985 return True
986
987 def ChromiumSyncCommandHandler(self):
988 """Handle a chromiumsync command arriving via http.
989
990 This covers all sync protocol commands: authentication, getupdates, and
991 commit.
992 """
993 test_name = "/chromiumsync/command"
994 if not self._ShouldHandleRequest(test_name):
995 return False
996
997 length = int(self.headers.getheader('content-length'))
998 raw_request = self.rfile.read(length)
999
pathorn@chromium.org44920122010-07-27 18:25:35 +00001000 if not self.server._sync_handler:
1001 import chromiumsync
1002 self.server._sync_handler = chromiumsync.TestServer()
1003 http_response, raw_reply = self.server._sync_handler.HandleCommand(
nick@chromium.org06b8a662010-09-22 22:50:18 +00001004 self.path, raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001005 self.send_response(http_response)
1006 self.end_headers()
1007 self.wfile.write(raw_reply)
1008 return True
1009
tony@chromium.org03266982010-03-05 03:18:42 +00001010 def MultipartHandler(self):
1011 """Send a multipart response (10 text/html pages)."""
1012 test_name = "/multipart"
1013 if not self._ShouldHandleRequest(test_name):
1014 return False
1015
1016 num_frames = 10
1017 bound = '12345'
1018 self.send_response(200)
1019 self.send_header('Content-type',
1020 'multipart/x-mixed-replace;boundary=' + bound)
1021 self.end_headers()
1022
1023 for i in xrange(num_frames):
1024 self.wfile.write('--' + bound + '\r\n')
1025 self.wfile.write('Content-type: text/html\r\n\r\n')
1026 self.wfile.write('<title>page ' + str(i) + '</title>')
1027 self.wfile.write('page ' + str(i))
1028
1029 self.wfile.write('--' + bound + '--')
1030 return True
1031
initial.commit94958cf2008-07-26 22:42:52 +00001032 def DefaultResponseHandler(self):
1033 """This is the catch-all response handler for requests that aren't handled
1034 by one of the special handlers above.
1035 Note that we specify the content-length as without it the https connection
1036 is not closed properly (and the browser keeps expecting data)."""
1037
1038 contents = "Default response given for path: " + self.path
1039 self.send_response(200)
1040 self.send_header('Content-type', 'text/html')
1041 self.send_header("Content-Length", len(contents))
1042 self.end_headers()
1043 self.wfile.write(contents)
1044 return True
1045
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001046 def RedirectConnectHandler(self):
1047 """Sends a redirect to the CONNECT request for www.redirect.com. This
1048 response is not specified by the RFC, so the browser should not follow
1049 the redirect."""
1050
1051 if (self.path.find("www.redirect.com") < 0):
1052 return False
1053
1054 dest = "http://www.destination.com/foo.js"
1055
1056 self.send_response(302) # moved temporarily
1057 self.send_header('Location', dest)
1058 self.send_header('Connection', 'close')
1059 self.end_headers()
1060 return True
1061
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001062 def ServerAuthConnectHandler(self):
1063 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1064 response doesn't make sense because the proxy server cannot request
1065 server authentication."""
1066
1067 if (self.path.find("www.server-auth.com") < 0):
1068 return False
1069
1070 challenge = 'Basic realm="WallyWorld"'
1071
1072 self.send_response(401) # unauthorized
1073 self.send_header('WWW-Authenticate', challenge)
1074 self.send_header('Connection', 'close')
1075 self.end_headers()
1076 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001077
1078 def DefaultConnectResponseHandler(self):
1079 """This is the catch-all response handler for CONNECT requests that aren't
1080 handled by one of the special handlers above. Real Web servers respond
1081 with 400 to CONNECT requests."""
1082
1083 contents = "Your client has issued a malformed or illegal request."
1084 self.send_response(400) # bad request
1085 self.send_header('Content-type', 'text/html')
1086 self.send_header("Content-Length", len(contents))
1087 self.end_headers()
1088 self.wfile.write(contents)
1089 return True
1090
1091 def do_CONNECT(self):
1092 for handler in self._connect_handlers:
1093 if handler():
1094 return
1095
initial.commit94958cf2008-07-26 22:42:52 +00001096 def do_GET(self):
1097 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001098 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001099 return
1100
1101 def do_POST(self):
1102 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001103 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001104 return
1105
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001106 def do_PUT(self):
1107 for handler in self._put_handlers:
1108 if handler():
1109 return
1110
initial.commit94958cf2008-07-26 22:42:52 +00001111 # called by the redirect handling function when there is no parameter
1112 def sendRedirectHelp(self, redirect_name):
1113 self.send_response(200)
1114 self.send_header('Content-type', 'text/html')
1115 self.end_headers()
1116 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1117 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1118 self.wfile.write('</body></html>')
1119
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001120def MakeDataDir():
1121 if options.data_dir:
1122 if not os.path.isdir(options.data_dir):
1123 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1124 return None
1125 my_data_dir = options.data_dir
1126 else:
1127 # Create the default path to our data dir, relative to the exe dir.
1128 my_data_dir = os.path.dirname(sys.argv[0])
1129 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001130 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001131
1132 #TODO(ibrar): Must use Find* funtion defined in google\tools
1133 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1134
1135 return my_data_dir
1136
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001137class FileMultiplexer:
1138 def __init__(self, fd1, fd2) :
1139 self.__fd1 = fd1
1140 self.__fd2 = fd2
1141
1142 def __del__(self) :
1143 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1144 self.__fd1.close()
1145 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1146 self.__fd2.close()
1147
1148 def write(self, text) :
1149 self.__fd1.write(text)
1150 self.__fd2.write(text)
1151
1152 def flush(self) :
1153 self.__fd1.flush()
1154 self.__fd2.flush()
1155
initial.commit94958cf2008-07-26 22:42:52 +00001156def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001157 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001158 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1159 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001160
1161 port = options.port
1162
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001163 if options.server_type == SERVER_HTTP:
1164 if options.cert:
1165 # let's make sure the cert file exists.
1166 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001167 print 'specified server cert file not found: ' + options.cert + \
1168 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001169 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001170 for ca_cert in options.ssl_client_ca:
1171 if not os.path.isfile(ca_cert):
1172 print 'specified trusted client CA file not found: ' + ca_cert + \
1173 ' exiting...'
1174 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001175 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001176 options.ssl_client_auth, options.ssl_client_ca,
1177 options.ssl_bulk_cipher)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001178 print 'HTTPS server started on port %d...' % port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001179 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001180 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001181 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001182
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001183 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001184 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001185 server._sync_handler = None
nick@chromium.org8daefd62010-10-27 23:24:05 +00001186
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001187 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001188 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001189 my_data_dir = MakeDataDir()
1190
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001191 # Instantiate a dummy authorizer for managing 'virtual' users
1192 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1193
1194 # Define a new user having full r/w permissions and a read-only
1195 # anonymous user
1196 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1197
1198 authorizer.add_anonymous(my_data_dir)
1199
1200 # Instantiate FTP handler class
1201 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1202 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001203
1204 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001205 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1206 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001207
1208 # Instantiate FTP server class and listen to 127.0.0.1:port
1209 address = ('127.0.0.1', port)
1210 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001211 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001212
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001213 # Notify the parent that we've started. (BaseServer subclasses
1214 # bind their sockets on construction.)
1215 if options.startup_pipe is not None:
1216 if sys.platform == 'win32':
1217 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1218 else:
1219 fd = options.startup_pipe
1220 startup_pipe = os.fdopen(fd, "w")
nick@chromium.org8daefd62010-10-27 23:24:05 +00001221 startup_pipe.write("READY")
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001222 startup_pipe.close()
1223
initial.commit94958cf2008-07-26 22:42:52 +00001224 try:
1225 server.serve_forever()
1226 except KeyboardInterrupt:
1227 print 'shutting down server'
1228 server.stop = True
1229
1230if __name__ == '__main__':
1231 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001232 option_parser.add_option("-f", '--ftp', action='store_const',
1233 const=SERVER_FTP, default=SERVER_HTTP,
1234 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001235 help='FTP or HTTP server: default is HTTP.')
nick@chromium.org8daefd62010-10-27 23:24:05 +00001236 option_parser.add_option('', '--port', default='8888', type='int',
1237 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001238 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001239 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001240 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001241 help='Specify that https should be used, specify '
1242 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001243 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001244 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1245 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001246 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1247 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001248 'should include the CA named in the subject of '
1249 'the DER-encoded certificate contained in the '
1250 'specified file. This option may appear multiple '
1251 'times, indicating multiple CA names should be '
1252 'sent in the request.')
1253 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1254 help='Specify the bulk encryption algorithm(s)'
1255 'that will be accepted by the SSL server. Valid '
1256 'values are "aes256", "aes128", "3des", "rc4". If '
1257 'omitted, all algorithms will be used. This '
1258 'option may appear multiple times, indicating '
1259 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001260 option_parser.add_option('', '--file-root-url', default='/files/',
1261 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001262 option_parser.add_option('', '--startup-pipe', type='int',
1263 dest='startup_pipe',
1264 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001265 options, args = option_parser.parse_args()
1266
1267 sys.exit(main(options, args))