blob: 55aa6a9a1e1a297f51108079e00af6c22ccac62c [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
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000024import urlparse
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
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000576 def _ReplaceFileData(self, data, query_parameters):
577 """Replaces matching substrings in a file.
578
579 If the 'replace_orig' and 'replace_new' URL query parameters are present,
580 a new string is returned with all occasions of the 'replace_orig' value
581 replaced by the 'replace_new' value.
582
583 If the parameters are not present, |data| is returned.
584 """
585 query_dict = cgi.parse_qs(query_parameters)
586 orig_values = query_dict.get('replace_orig', [])
587 new_values = query_dict.get('replace_new', [])
588 if not orig_values or not new_values:
589 return data
590 orig_value = orig_values[0]
591 new_value = new_values[0]
592 return data.replace(orig_value, new_value)
593
initial.commit94958cf2008-07-26 22:42:52 +0000594 def FileHandler(self):
595 """This handler sends the contents of the requested file. Wow, it's like
596 a real webserver!"""
597
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000598 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000599 if not self.path.startswith(prefix):
600 return False
601
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000602 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000603 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000604 self.rfile.read(int(self.headers.getheader('content-length')))
605
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000606 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
607 sub_path = url_path[len(prefix):]
608 entries = sub_path.split('/')
609 file_path = os.path.join(self.server.data_dir, *entries)
610 if os.path.isdir(file_path):
611 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000612
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000613 if not os.path.isfile(file_path):
614 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000615 self.send_error(404)
616 return True
617
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000618 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000619 data = f.read()
620 f.close()
621
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000622 data = self._ReplaceFileData(data, query)
623
initial.commit94958cf2008-07-26 22:42:52 +0000624 # If file.mock-http-headers exists, it contains the headers we
625 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000626 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000627 if os.path.isfile(headers_path):
628 f = open(headers_path, "r")
629
630 # "HTTP/1.1 200 OK"
631 response = f.readline()
632 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
633 self.send_response(int(status_code))
634
635 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000636 header_values = re.findall('(\S+):\s*(.*)', line)
637 if len(header_values) > 0:
638 # "name: value"
639 name, value = header_values[0]
640 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000641 f.close()
642 else:
643 # Could be more generic once we support mime-type sniffing, but for
644 # now we need to set it explicitly.
645 self.send_response(200)
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000646 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
initial.commit94958cf2008-07-26 22:42:52 +0000647 self.send_header('Content-Length', len(data))
648 self.end_headers()
649
650 self.wfile.write(data)
651
652 return True
653
654 def RealFileWithCommonHeaderHandler(self):
655 """This handler sends the contents of the requested file without the pseudo
656 http head!"""
657
658 prefix='/realfiles/'
659 if not self.path.startswith(prefix):
660 return False
661
662 file = self.path[len(prefix):]
663 path = os.path.join(self.server.data_dir, file)
664
665 try:
666 f = open(path, "rb")
667 data = f.read()
668 f.close()
669
670 # just simply set the MIME as octal stream
671 self.send_response(200)
672 self.send_header('Content-type', 'application/octet-stream')
673 self.end_headers()
674
675 self.wfile.write(data)
676 except:
677 self.send_error(404)
678
679 return True
680
681 def RealBZ2FileWithCommonHeaderHandler(self):
682 """This handler sends the bzip2 contents of the requested file with
683 corresponding Content-Encoding field in http head!"""
684
685 prefix='/realbz2files/'
686 if not self.path.startswith(prefix):
687 return False
688
689 parts = self.path.split('?')
690 file = parts[0][len(prefix):]
691 path = os.path.join(self.server.data_dir, file) + '.bz2'
692
693 if len(parts) > 1:
694 options = parts[1]
695 else:
696 options = ''
697
698 try:
699 self.send_response(200)
700 accept_encoding = self.headers.get("Accept-Encoding")
701 if accept_encoding.find("bzip2") != -1:
702 f = open(path, "rb")
703 data = f.read()
704 f.close()
705 self.send_header('Content-Encoding', 'bzip2')
706 self.send_header('Content-type', 'application/x-bzip2')
707 self.end_headers()
708 if options == 'incremental-header':
709 self.wfile.write(data[:1])
710 self.wfile.flush()
711 time.sleep(1.0)
712 self.wfile.write(data[1:])
713 else:
714 self.wfile.write(data)
715 else:
716 """client do not support bzip2 format, send pseudo content
717 """
718 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
719 self.end_headers()
720 self.wfile.write("you do not support bzip2 encoding")
721 except:
722 self.send_error(404)
723
724 return True
725
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000726 def SetCookieHandler(self):
727 """This handler just sets a cookie, for testing cookie handling."""
728
729 if not self._ShouldHandleRequest("/set-cookie"):
730 return False
731
732 query_char = self.path.find('?')
733 if query_char != -1:
734 cookie_values = self.path[query_char + 1:].split('&')
735 else:
736 cookie_values = ("",)
737 self.send_response(200)
738 self.send_header('Content-type', 'text/html')
739 for cookie_value in cookie_values:
740 self.send_header('Set-Cookie', '%s' % cookie_value)
741 self.end_headers()
742 for cookie_value in cookie_values:
743 self.wfile.write('%s' % cookie_value)
744 return True
745
initial.commit94958cf2008-07-26 22:42:52 +0000746 def AuthBasicHandler(self):
747 """This handler tests 'Basic' authentication. It just sends a page with
748 title 'user/pass' if you succeed."""
749
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000750 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000751 return False
752
753 username = userpass = password = b64str = ""
754
ericroman@google.com239b4d82009-03-27 04:00:22 +0000755 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
756
initial.commit94958cf2008-07-26 22:42:52 +0000757 auth = self.headers.getheader('authorization')
758 try:
759 if not auth:
760 raise Exception('no auth')
761 b64str = re.findall(r'Basic (\S+)', auth)[0]
762 userpass = base64.b64decode(b64str)
763 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
764 if password != 'secret':
765 raise Exception('wrong password')
766 except Exception, e:
767 # Authentication failed.
768 self.send_response(401)
769 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
770 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000771 if set_cookie_if_challenged:
772 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000773 self.end_headers()
774 self.wfile.write('<html><head>')
775 self.wfile.write('<title>Denied: %s</title>' % e)
776 self.wfile.write('</head><body>')
777 self.wfile.write('auth=%s<p>' % auth)
778 self.wfile.write('b64str=%s<p>' % b64str)
779 self.wfile.write('username: %s<p>' % username)
780 self.wfile.write('userpass: %s<p>' % userpass)
781 self.wfile.write('password: %s<p>' % password)
782 self.wfile.write('You sent:<br>%s<p>' % self.headers)
783 self.wfile.write('</body></html>')
784 return True
785
786 # Authentication successful. (Return a cachable response to allow for
787 # testing cached pages that require authentication.)
788 if_none_match = self.headers.getheader('if-none-match')
789 if if_none_match == "abc":
790 self.send_response(304)
791 self.end_headers()
792 else:
793 self.send_response(200)
794 self.send_header('Content-type', 'text/html')
795 self.send_header('Cache-control', 'max-age=60000')
796 self.send_header('Etag', 'abc')
797 self.end_headers()
798 self.wfile.write('<html><head>')
799 self.wfile.write('<title>%s/%s</title>' % (username, password))
800 self.wfile.write('</head><body>')
801 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000802 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000803 self.wfile.write('</body></html>')
804
805 return True
806
tonyg@chromium.org75054202010-03-31 22:06:10 +0000807 def GetNonce(self, force_reset=False):
808 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000809
tonyg@chromium.org75054202010-03-31 22:06:10 +0000810 This is a fake implementation. A real implementation would only use a given
811 nonce a single time (hence the name n-once). However, for the purposes of
812 unittesting, we don't care about the security of the nonce.
813
814 Args:
815 force_reset: Iff set, the nonce will be changed. Useful for testing the
816 "stale" response.
817 """
818 if force_reset or not self.server.nonce_time:
819 self.server.nonce_time = time.time()
820 return _new_md5('privatekey%s%d' %
821 (self.path, self.server.nonce_time)).hexdigest()
822
823 def AuthDigestHandler(self):
824 """This handler tests 'Digest' authentication.
825
826 It just sends a page with title 'user/pass' if you succeed.
827
828 A stale response is sent iff "stale" is present in the request path.
829 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000830 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000831 return False
832
tonyg@chromium.org75054202010-03-31 22:06:10 +0000833 stale = 'stale' in self.path
834 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000835 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000836 password = 'secret'
837 realm = 'testrealm'
838
839 auth = self.headers.getheader('authorization')
840 pairs = {}
841 try:
842 if not auth:
843 raise Exception('no auth')
844 if not auth.startswith('Digest'):
845 raise Exception('not digest')
846 # Pull out all the name="value" pairs as a dictionary.
847 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
848
849 # Make sure it's all valid.
850 if pairs['nonce'] != nonce:
851 raise Exception('wrong nonce')
852 if pairs['opaque'] != opaque:
853 raise Exception('wrong opaque')
854
855 # Check the 'response' value and make sure it matches our magic hash.
856 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000857 hash_a1 = _new_md5(
858 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000859 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000860 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000861 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000862 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
863 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000864 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000865
866 if pairs['response'] != response:
867 raise Exception('wrong password')
868 except Exception, e:
869 # Authentication failed.
870 self.send_response(401)
871 hdr = ('Digest '
872 'realm="%s", '
873 'domain="/", '
874 'qop="auth", '
875 'algorithm=MD5, '
876 'nonce="%s", '
877 'opaque="%s"') % (realm, nonce, opaque)
878 if stale:
879 hdr += ', stale="TRUE"'
880 self.send_header('WWW-Authenticate', hdr)
881 self.send_header('Content-type', 'text/html')
882 self.end_headers()
883 self.wfile.write('<html><head>')
884 self.wfile.write('<title>Denied: %s</title>' % e)
885 self.wfile.write('</head><body>')
886 self.wfile.write('auth=%s<p>' % auth)
887 self.wfile.write('pairs=%s<p>' % pairs)
888 self.wfile.write('You sent:<br>%s<p>' % self.headers)
889 self.wfile.write('We are replying:<br>%s<p>' % hdr)
890 self.wfile.write('</body></html>')
891 return True
892
893 # Authentication successful.
894 self.send_response(200)
895 self.send_header('Content-type', 'text/html')
896 self.end_headers()
897 self.wfile.write('<html><head>')
898 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
899 self.wfile.write('</head><body>')
900 self.wfile.write('auth=%s<p>' % auth)
901 self.wfile.write('pairs=%s<p>' % pairs)
902 self.wfile.write('</body></html>')
903
904 return True
905
906 def SlowServerHandler(self):
907 """Wait for the user suggested time before responding. The syntax is
908 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000909 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000910 return False
911 query_char = self.path.find('?')
912 wait_sec = 1.0
913 if query_char >= 0:
914 try:
915 wait_sec = int(self.path[query_char + 1:])
916 except ValueError:
917 pass
918 time.sleep(wait_sec)
919 self.send_response(200)
920 self.send_header('Content-type', 'text/plain')
921 self.end_headers()
922 self.wfile.write("waited %d seconds" % wait_sec)
923 return True
924
925 def ContentTypeHandler(self):
926 """Returns a string of html with the given content type. E.g.,
927 /contenttype?text/css returns an html file with the Content-Type
928 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000929 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000930 return False
931 query_char = self.path.find('?')
932 content_type = self.path[query_char + 1:].strip()
933 if not content_type:
934 content_type = 'text/html'
935 self.send_response(200)
936 self.send_header('Content-Type', content_type)
937 self.end_headers()
938 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
939 return True
940
941 def ServerRedirectHandler(self):
942 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000943 '/server-redirect?http://foo.bar/asdf' to redirect to
944 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000945
946 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000947 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000948 return False
949
950 query_char = self.path.find('?')
951 if query_char < 0 or len(self.path) <= query_char + 1:
952 self.sendRedirectHelp(test_name)
953 return True
954 dest = self.path[query_char + 1:]
955
956 self.send_response(301) # moved permanently
957 self.send_header('Location', dest)
958 self.send_header('Content-type', 'text/html')
959 self.end_headers()
960 self.wfile.write('<html><head>')
961 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
962
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000963 return True
initial.commit94958cf2008-07-26 22:42:52 +0000964
965 def ClientRedirectHandler(self):
966 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000967 '/client-redirect?http://foo.bar/asdf' to redirect to
968 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000969
970 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000971 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000972 return False
973
974 query_char = self.path.find('?');
975 if query_char < 0 or len(self.path) <= query_char + 1:
976 self.sendRedirectHelp(test_name)
977 return True
978 dest = self.path[query_char + 1:]
979
980 self.send_response(200)
981 self.send_header('Content-type', 'text/html')
982 self.end_headers()
983 self.wfile.write('<html><head>')
984 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
985 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
986
987 return True
988
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000989 def ChromiumSyncTimeHandler(self):
990 """Handle Chromium sync .../time requests.
991
992 The syncer sometimes checks server reachability by examining /time.
993 """
994 test_name = "/chromiumsync/time"
995 if not self._ShouldHandleRequest(test_name):
996 return False
997
998 self.send_response(200)
999 self.send_header('Content-type', 'text/html')
1000 self.end_headers()
1001 return True
1002
1003 def ChromiumSyncCommandHandler(self):
1004 """Handle a chromiumsync command arriving via http.
1005
1006 This covers all sync protocol commands: authentication, getupdates, and
1007 commit.
1008 """
1009 test_name = "/chromiumsync/command"
1010 if not self._ShouldHandleRequest(test_name):
1011 return False
1012
1013 length = int(self.headers.getheader('content-length'))
1014 raw_request = self.rfile.read(length)
1015
pathorn@chromium.org44920122010-07-27 18:25:35 +00001016 if not self.server._sync_handler:
1017 import chromiumsync
1018 self.server._sync_handler = chromiumsync.TestServer()
1019 http_response, raw_reply = self.server._sync_handler.HandleCommand(
nick@chromium.org06b8a662010-09-22 22:50:18 +00001020 self.path, raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001021 self.send_response(http_response)
1022 self.end_headers()
1023 self.wfile.write(raw_reply)
1024 return True
1025
tony@chromium.org03266982010-03-05 03:18:42 +00001026 def MultipartHandler(self):
1027 """Send a multipart response (10 text/html pages)."""
1028 test_name = "/multipart"
1029 if not self._ShouldHandleRequest(test_name):
1030 return False
1031
1032 num_frames = 10
1033 bound = '12345'
1034 self.send_response(200)
1035 self.send_header('Content-type',
1036 'multipart/x-mixed-replace;boundary=' + bound)
1037 self.end_headers()
1038
1039 for i in xrange(num_frames):
1040 self.wfile.write('--' + bound + '\r\n')
1041 self.wfile.write('Content-type: text/html\r\n\r\n')
1042 self.wfile.write('<title>page ' + str(i) + '</title>')
1043 self.wfile.write('page ' + str(i))
1044
1045 self.wfile.write('--' + bound + '--')
1046 return True
1047
initial.commit94958cf2008-07-26 22:42:52 +00001048 def DefaultResponseHandler(self):
1049 """This is the catch-all response handler for requests that aren't handled
1050 by one of the special handlers above.
1051 Note that we specify the content-length as without it the https connection
1052 is not closed properly (and the browser keeps expecting data)."""
1053
1054 contents = "Default response given for path: " + self.path
1055 self.send_response(200)
1056 self.send_header('Content-type', 'text/html')
1057 self.send_header("Content-Length", len(contents))
1058 self.end_headers()
1059 self.wfile.write(contents)
1060 return True
1061
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001062 def RedirectConnectHandler(self):
1063 """Sends a redirect to the CONNECT request for www.redirect.com. This
1064 response is not specified by the RFC, so the browser should not follow
1065 the redirect."""
1066
1067 if (self.path.find("www.redirect.com") < 0):
1068 return False
1069
1070 dest = "http://www.destination.com/foo.js"
1071
1072 self.send_response(302) # moved temporarily
1073 self.send_header('Location', dest)
1074 self.send_header('Connection', 'close')
1075 self.end_headers()
1076 return True
1077
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001078 def ServerAuthConnectHandler(self):
1079 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1080 response doesn't make sense because the proxy server cannot request
1081 server authentication."""
1082
1083 if (self.path.find("www.server-auth.com") < 0):
1084 return False
1085
1086 challenge = 'Basic realm="WallyWorld"'
1087
1088 self.send_response(401) # unauthorized
1089 self.send_header('WWW-Authenticate', challenge)
1090 self.send_header('Connection', 'close')
1091 self.end_headers()
1092 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001093
1094 def DefaultConnectResponseHandler(self):
1095 """This is the catch-all response handler for CONNECT requests that aren't
1096 handled by one of the special handlers above. Real Web servers respond
1097 with 400 to CONNECT requests."""
1098
1099 contents = "Your client has issued a malformed or illegal request."
1100 self.send_response(400) # bad request
1101 self.send_header('Content-type', 'text/html')
1102 self.send_header("Content-Length", len(contents))
1103 self.end_headers()
1104 self.wfile.write(contents)
1105 return True
1106
1107 def do_CONNECT(self):
1108 for handler in self._connect_handlers:
1109 if handler():
1110 return
1111
initial.commit94958cf2008-07-26 22:42:52 +00001112 def do_GET(self):
1113 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001114 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001115 return
1116
1117 def do_POST(self):
1118 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001119 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001120 return
1121
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001122 def do_PUT(self):
1123 for handler in self._put_handlers:
1124 if handler():
1125 return
1126
initial.commit94958cf2008-07-26 22:42:52 +00001127 # called by the redirect handling function when there is no parameter
1128 def sendRedirectHelp(self, redirect_name):
1129 self.send_response(200)
1130 self.send_header('Content-type', 'text/html')
1131 self.end_headers()
1132 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1133 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1134 self.wfile.write('</body></html>')
1135
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001136def MakeDataDir():
1137 if options.data_dir:
1138 if not os.path.isdir(options.data_dir):
1139 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1140 return None
1141 my_data_dir = options.data_dir
1142 else:
1143 # Create the default path to our data dir, relative to the exe dir.
1144 my_data_dir = os.path.dirname(sys.argv[0])
1145 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001146 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001147
1148 #TODO(ibrar): Must use Find* funtion defined in google\tools
1149 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1150
1151 return my_data_dir
1152
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001153class FileMultiplexer:
1154 def __init__(self, fd1, fd2) :
1155 self.__fd1 = fd1
1156 self.__fd2 = fd2
1157
1158 def __del__(self) :
1159 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1160 self.__fd1.close()
1161 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1162 self.__fd2.close()
1163
1164 def write(self, text) :
1165 self.__fd1.write(text)
1166 self.__fd2.write(text)
1167
1168 def flush(self) :
1169 self.__fd1.flush()
1170 self.__fd2.flush()
1171
initial.commit94958cf2008-07-26 22:42:52 +00001172def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001173 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001174 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1175 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001176
1177 port = options.port
1178
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001179 if options.server_type == SERVER_HTTP:
1180 if options.cert:
1181 # let's make sure the cert file exists.
1182 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001183 print 'specified server cert file not found: ' + options.cert + \
1184 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001185 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001186 for ca_cert in options.ssl_client_ca:
1187 if not os.path.isfile(ca_cert):
1188 print 'specified trusted client CA file not found: ' + ca_cert + \
1189 ' exiting...'
1190 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001191 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001192 options.ssl_client_auth, options.ssl_client_ca,
1193 options.ssl_bulk_cipher)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001194 print 'HTTPS server started on port %d...' % port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001195 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001196 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001197 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001198
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001199 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001200 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001201 server._sync_handler = None
nick@chromium.org8daefd62010-10-27 23:24:05 +00001202
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001203 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001204 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001205 my_data_dir = MakeDataDir()
1206
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001207 # Instantiate a dummy authorizer for managing 'virtual' users
1208 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1209
1210 # Define a new user having full r/w permissions and a read-only
1211 # anonymous user
1212 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1213
1214 authorizer.add_anonymous(my_data_dir)
1215
1216 # Instantiate FTP handler class
1217 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1218 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001219
1220 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001221 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1222 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001223
1224 # Instantiate FTP server class and listen to 127.0.0.1:port
1225 address = ('127.0.0.1', port)
1226 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001227 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001228
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001229 # Notify the parent that we've started. (BaseServer subclasses
1230 # bind their sockets on construction.)
1231 if options.startup_pipe is not None:
1232 if sys.platform == 'win32':
1233 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1234 else:
1235 fd = options.startup_pipe
1236 startup_pipe = os.fdopen(fd, "w")
nick@chromium.org8daefd62010-10-27 23:24:05 +00001237 startup_pipe.write("READY")
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001238 startup_pipe.close()
1239
initial.commit94958cf2008-07-26 22:42:52 +00001240 try:
1241 server.serve_forever()
1242 except KeyboardInterrupt:
1243 print 'shutting down server'
1244 server.stop = True
1245
1246if __name__ == '__main__':
1247 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001248 option_parser.add_option("-f", '--ftp', action='store_const',
1249 const=SERVER_FTP, default=SERVER_HTTP,
1250 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001251 help='FTP or HTTP server: default is HTTP.')
nick@chromium.org8daefd62010-10-27 23:24:05 +00001252 option_parser.add_option('', '--port', default='8888', type='int',
1253 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001254 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001255 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001256 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001257 help='Specify that https should be used, specify '
1258 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001259 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001260 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1261 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001262 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1263 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001264 'should include the CA named in the subject of '
1265 'the DER-encoded certificate contained in the '
1266 'specified file. This option may appear multiple '
1267 'times, indicating multiple CA names should be '
1268 'sent in the request.')
1269 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1270 help='Specify the bulk encryption algorithm(s)'
1271 'that will be accepted by the SSL server. Valid '
1272 'values are "aes256", "aes128", "3des", "rc4". If '
1273 'omitted, all algorithms will be used. This '
1274 'option may appear multiple times, indicating '
1275 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001276 option_parser.add_option('', '--file-root-url', default='/files/',
1277 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001278 option_parser.add_option('', '--startup-pipe', type='int',
1279 dest='startup_pipe',
1280 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001281 options, args = option_parser.parse_args()
1282
1283 sys.exit(main(options, args))