blob: 8e3df5ea4a345996c1e86ca6b92af6ca9ad2389d [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
nick@chromium.org2d78c222010-04-30 05:37:51 +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.
9It defaults to living on localhost:8888.
10It 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.
12To shut it down properly, visit localhost:8888/kill.
13"""
14
15import base64
16import BaseHTTPServer
17import cgi
initial.commit94958cf2008-07-26 22:42:52 +000018import optparse
19import os
20import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000021import shutil
initial.commit94958cf2008-07-26 22:42:52 +000022import SocketServer
23import sys
24import time
nick@chromium.org2d78c222010-04-30 05:37:51 +000025import urllib2
26
27import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000028import tlslite
29import tlslite.api
nick@chromium.org2d78c222010-04-30 05:37:51 +000030
31import chromiumsync
erikkay@google.comd5182ff2009-01-08 20:45:27 +000032
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000033try:
34 import hashlib
35 _new_md5 = hashlib.md5
36except ImportError:
37 import md5
38 _new_md5 = md5.new
39
maruel@chromium.org756cf982009-03-05 12:46:38 +000040SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000041SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000042
43debug_output = sys.stderr
44def debug(str):
45 debug_output.write(str + "\n")
46 debug_output.flush()
47
48class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
49 """This is a specialization of of BaseHTTPServer to allow it
50 to be exited cleanly (by setting its "stop" member to True)."""
51
52 def serve_forever(self):
53 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000054 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000055 while not self.stop:
56 self.handle_request()
57 self.socket.close()
58
59class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
60 """This is a specialization of StoppableHTTPerver that add https support."""
61
62 def __init__(self, server_address, request_hander_class, cert_path):
63 s = open(cert_path).read()
64 x509 = tlslite.api.X509()
65 x509.parse(s)
66 self.cert_chain = tlslite.api.X509CertChain([x509])
67 s = open(cert_path).read()
68 self.private_key = tlslite.api.parsePEMKey(s, private=True)
69
70 self.session_cache = tlslite.api.SessionCache()
71 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
72
73 def handshake(self, tlsConnection):
74 """Creates the SSL connection."""
75 try:
76 tlsConnection.handshakeServer(certChain=self.cert_chain,
77 privateKey=self.private_key,
78 sessionCache=self.session_cache)
79 tlsConnection.ignoreAbruptClose = True
80 return True
81 except tlslite.api.TLSError, error:
82 print "Handshake failure:", str(error)
83 return False
84
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +000085class ForkingHTTPServer(SocketServer.ForkingMixIn, StoppableHTTPServer):
86 """This is a specialization of of StoppableHTTPServer which serves each
87 request in a separate process"""
88 pass
89
90class ForkingHTTPSServer(SocketServer.ForkingMixIn, HTTPSServer):
91 """This is a specialization of of HTTPSServer which serves each
92 request in a separate process"""
93 pass
94
initial.commit94958cf2008-07-26 22:42:52 +000095class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
96
97 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000098 self._connect_handlers = [
99 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000100 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000101 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000102 self._get_handlers = [
103 self.KillHandler,
104 self.NoCacheMaxAgeTimeHandler,
105 self.NoCacheTimeHandler,
106 self.CacheTimeHandler,
107 self.CacheExpiresHandler,
108 self.CacheProxyRevalidateHandler,
109 self.CachePrivateHandler,
110 self.CachePublicHandler,
111 self.CacheSMaxAgeHandler,
112 self.CacheMustRevalidateHandler,
113 self.CacheMustRevalidateMaxAgeHandler,
114 self.CacheNoStoreHandler,
115 self.CacheNoStoreMaxAgeHandler,
116 self.CacheNoTransformHandler,
117 self.DownloadHandler,
118 self.DownloadFinishHandler,
119 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000120 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000121 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000122 self.FileHandler,
123 self.RealFileWithCommonHeaderHandler,
124 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000125 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000126 self.AuthBasicHandler,
127 self.AuthDigestHandler,
128 self.SlowServerHandler,
129 self.ContentTypeHandler,
130 self.ServerRedirectHandler,
131 self.ClientRedirectHandler,
nick@chromium.org2d78c222010-04-30 05:37:51 +0000132 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000133 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000134 self.DefaultResponseHandler]
135 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000136 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000137 self.EchoTitleHandler,
138 self.EchoAllHandler,
nick@chromium.org2d78c222010-04-30 05:37:51 +0000139 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000140 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000141 self._put_handlers = [
142 self.WriteFile,
143 self.EchoTitleHandler,
144 self.EchoAllHandler,
145 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000146
maruel@google.come250a9b2009-03-10 17:39:46 +0000147 self._mime_types = {
148 'gif': 'image/gif',
149 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000150 'jpg' : 'image/jpeg',
151 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000152 }
initial.commit94958cf2008-07-26 22:42:52 +0000153 self._default_mime_type = 'text/html'
154
maruel@google.come250a9b2009-03-10 17:39:46 +0000155 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
156 client_address,
157 socket_server)
nick@chromium.org2d78c222010-04-30 05:37:51 +0000158 # Class variable; shared across requests.
159 _sync_handler = chromiumsync.TestServer()
initial.commit94958cf2008-07-26 22:42:52 +0000160
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000161 def _ShouldHandleRequest(self, handler_name):
162 """Determines if the path can be handled by the handler.
163
164 We consider a handler valid if the path begins with the
165 handler name. It can optionally be followed by "?*", "/*".
166 """
167
168 pattern = re.compile('%s($|\?|/).*' % handler_name)
169 return pattern.match(self.path)
170
initial.commit94958cf2008-07-26 22:42:52 +0000171 def GetMIMETypeFromName(self, file_name):
172 """Returns the mime type for the specified file_name. So far it only looks
173 at the file extension."""
174
175 (shortname, extension) = os.path.splitext(file_name)
176 if len(extension) == 0:
177 # no extension.
178 return self._default_mime_type
179
ericroman@google.comc17ca532009-05-07 03:51:05 +0000180 # extension starts with a dot, so we need to remove it
181 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000182
183 def KillHandler(self):
184 """This request handler kills the server, for use when we're done"
185 with the a particular test."""
186
187 if (self.path.find("kill") < 0):
188 return False
189
190 self.send_response(200)
191 self.send_header('Content-type', 'text/html')
192 self.send_header('Cache-Control', 'max-age=0')
193 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000194 if options.never_die:
195 self.wfile.write('I cannot die!! BWAHAHA')
196 else:
197 self.wfile.write('Goodbye cruel world!')
198 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000199
200 return True
201
202 def NoCacheMaxAgeTimeHandler(self):
203 """This request handler yields a page with the title set to the current
204 system time, and no caching requested."""
205
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000206 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000207 return False
208
209 self.send_response(200)
210 self.send_header('Cache-Control', 'max-age=0')
211 self.send_header('Content-type', 'text/html')
212 self.end_headers()
213
maruel@google.come250a9b2009-03-10 17:39:46 +0000214 self.wfile.write('<html><head><title>%s</title></head></html>' %
215 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000216
217 return True
218
219 def NoCacheTimeHandler(self):
220 """This request handler yields a page with the title set to the current
221 system time, and no caching requested."""
222
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000223 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000224 return False
225
226 self.send_response(200)
227 self.send_header('Cache-Control', 'no-cache')
228 self.send_header('Content-type', 'text/html')
229 self.end_headers()
230
maruel@google.come250a9b2009-03-10 17:39:46 +0000231 self.wfile.write('<html><head><title>%s</title></head></html>' %
232 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000233
234 return True
235
236 def CacheTimeHandler(self):
237 """This request handler yields a page with the title set to the current
238 system time, and allows caching for one minute."""
239
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000240 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000241 return False
242
243 self.send_response(200)
244 self.send_header('Cache-Control', 'max-age=60')
245 self.send_header('Content-type', 'text/html')
246 self.end_headers()
247
maruel@google.come250a9b2009-03-10 17:39:46 +0000248 self.wfile.write('<html><head><title>%s</title></head></html>' %
249 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000250
251 return True
252
253 def CacheExpiresHandler(self):
254 """This request handler yields a page with the title set to the current
255 system time, and set the page to expire on 1 Jan 2099."""
256
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000257 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000258 return False
259
260 self.send_response(200)
261 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
262 self.send_header('Content-type', 'text/html')
263 self.end_headers()
264
maruel@google.come250a9b2009-03-10 17:39:46 +0000265 self.wfile.write('<html><head><title>%s</title></head></html>' %
266 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000267
268 return True
269
270 def CacheProxyRevalidateHandler(self):
271 """This request handler yields a page with the title set to the current
272 system time, and allows caching for 60 seconds"""
273
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000274 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000275 return False
276
277 self.send_response(200)
278 self.send_header('Content-type', 'text/html')
279 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
280 self.end_headers()
281
maruel@google.come250a9b2009-03-10 17:39:46 +0000282 self.wfile.write('<html><head><title>%s</title></head></html>' %
283 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000284
285 return True
286
287 def CachePrivateHandler(self):
288 """This request handler yields a page with the title set to the current
289 system time, and allows caching for 5 seconds."""
290
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000291 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000292 return False
293
294 self.send_response(200)
295 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000296 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000297 self.end_headers()
298
maruel@google.come250a9b2009-03-10 17:39:46 +0000299 self.wfile.write('<html><head><title>%s</title></head></html>' %
300 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000301
302 return True
303
304 def CachePublicHandler(self):
305 """This request handler yields a page with the title set to the current
306 system time, and allows caching for 5 seconds."""
307
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000308 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000309 return False
310
311 self.send_response(200)
312 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000313 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000314 self.end_headers()
315
maruel@google.come250a9b2009-03-10 17:39:46 +0000316 self.wfile.write('<html><head><title>%s</title></head></html>' %
317 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000318
319 return True
320
321 def CacheSMaxAgeHandler(self):
322 """This request handler yields a page with the title set to the current
323 system time, and does not allow for caching."""
324
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000325 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000326 return False
327
328 self.send_response(200)
329 self.send_header('Content-type', 'text/html')
330 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
331 self.end_headers()
332
maruel@google.come250a9b2009-03-10 17:39:46 +0000333 self.wfile.write('<html><head><title>%s</title></head></html>' %
334 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000335
336 return True
337
338 def CacheMustRevalidateHandler(self):
339 """This request handler yields a page with the title set to the current
340 system time, and does not allow caching."""
341
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000342 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000343 return False
344
345 self.send_response(200)
346 self.send_header('Content-type', 'text/html')
347 self.send_header('Cache-Control', 'must-revalidate')
348 self.end_headers()
349
maruel@google.come250a9b2009-03-10 17:39:46 +0000350 self.wfile.write('<html><head><title>%s</title></head></html>' %
351 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000352
353 return True
354
355 def CacheMustRevalidateMaxAgeHandler(self):
356 """This request handler yields a page with the title set to the current
357 system time, and does not allow caching event though max-age of 60
358 seconds is specified."""
359
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000360 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000361 return False
362
363 self.send_response(200)
364 self.send_header('Content-type', 'text/html')
365 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
366 self.end_headers()
367
maruel@google.come250a9b2009-03-10 17:39:46 +0000368 self.wfile.write('<html><head><title>%s</title></head></html>' %
369 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000370
371 return True
372
initial.commit94958cf2008-07-26 22:42:52 +0000373 def CacheNoStoreHandler(self):
374 """This request handler yields a page with the title set to the current
375 system time, and does not allow the page to be stored."""
376
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000377 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000378 return False
379
380 self.send_response(200)
381 self.send_header('Content-type', 'text/html')
382 self.send_header('Cache-Control', 'no-store')
383 self.end_headers()
384
maruel@google.come250a9b2009-03-10 17:39:46 +0000385 self.wfile.write('<html><head><title>%s</title></head></html>' %
386 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000387
388 return True
389
390 def CacheNoStoreMaxAgeHandler(self):
391 """This request handler yields a page with the title set to the current
392 system time, and does not allow the page to be stored even though max-age
393 of 60 seconds is specified."""
394
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000395 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000396 return False
397
398 self.send_response(200)
399 self.send_header('Content-type', 'text/html')
400 self.send_header('Cache-Control', 'max-age=60, no-store')
401 self.end_headers()
402
maruel@google.come250a9b2009-03-10 17:39:46 +0000403 self.wfile.write('<html><head><title>%s</title></head></html>' %
404 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000405
406 return True
407
408
409 def CacheNoTransformHandler(self):
410 """This request handler yields a page with the title set to the current
411 system time, and does not allow the content to transformed during
412 user-agent caching"""
413
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000414 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000415 return False
416
417 self.send_response(200)
418 self.send_header('Content-type', 'text/html')
419 self.send_header('Cache-Control', 'no-transform')
420 self.end_headers()
421
maruel@google.come250a9b2009-03-10 17:39:46 +0000422 self.wfile.write('<html><head><title>%s</title></head></html>' %
423 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000424
425 return True
426
427 def EchoHeader(self):
428 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000429 """The only difference between this function and the EchoHeaderOverride"""
430 """function is in the parameter being passed to the helper function"""
431 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000432
ananta@chromium.org219b2062009-10-23 16:09:41 +0000433 def EchoHeaderOverride(self):
434 """This handler echoes back the value of a specific request header."""
435 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
436 """IE to issue HTTP requests using the host network stack."""
437 """The Accept and Charset tests which expect the server to echo back"""
438 """the corresponding headers fail here as IE returns cached responses"""
439 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
440 """treats this request as a new request and does not cache it."""
441 return self.EchoHeaderHelper("/echoheaderoverride")
442
443 def EchoHeaderHelper(self, echo_header):
444 """This function echoes back the value of the request header passed in."""
445 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000446 return False
447
448 query_char = self.path.find('?')
449 if query_char != -1:
450 header_name = self.path[query_char+1:]
451
452 self.send_response(200)
453 self.send_header('Content-type', 'text/plain')
454 self.send_header('Cache-control', 'max-age=60000')
455 # insert a vary header to properly indicate that the cachability of this
456 # request is subject to value of the request header being echoed.
457 if len(header_name) > 0:
458 self.send_header('Vary', header_name)
459 self.end_headers()
460
461 if len(header_name) > 0:
462 self.wfile.write(self.headers.getheader(header_name))
463
464 return True
465
466 def EchoHandler(self):
467 """This handler just echoes back the payload of the request, for testing
468 form submission."""
469
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000470 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000471 return False
472
473 self.send_response(200)
474 self.send_header('Content-type', 'text/html')
475 self.end_headers()
476 length = int(self.headers.getheader('content-length'))
477 request = self.rfile.read(length)
478 self.wfile.write(request)
479 return True
480
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000481 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000482 """This is handler dumps the content of POST/PUT request to a disk file
483 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000484
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000485 prefix='/writefile/'
486 if not self.path.startswith(prefix):
487 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000488
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000489 file_name = self.path[len(prefix):]
490
491 # do not allow fancy chars in file name
492 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
493 if len(file_name) and file_name[0] != '.':
494 path = os.path.join(self.server.data_dir, 'dump', file_name);
495 length = int(self.headers.getheader('content-length'))
496 request = self.rfile.read(length)
497 f = open(path, "wb")
498 f.write(request);
499 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000500
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000501 self.send_response(200)
502 self.send_header('Content-type', 'text/html')
503 self.end_headers()
504 self.wfile.write('<html>%s</html>' % file_name)
505 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000506
initial.commit94958cf2008-07-26 22:42:52 +0000507 def EchoTitleHandler(self):
508 """This handler is like Echo, but sets the page title to the request."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
514 self.send_header('Content-type', 'text/html')
515 self.end_headers()
516 length = int(self.headers.getheader('content-length'))
517 request = self.rfile.read(length)
518 self.wfile.write('<html><head><title>')
519 self.wfile.write(request)
520 self.wfile.write('</title></head></html>')
521 return True
522
523 def EchoAllHandler(self):
524 """This handler yields a (more) human-readable page listing information
525 about the request header & contents."""
526
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000527 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000528 return False
529
530 self.send_response(200)
531 self.send_header('Content-type', 'text/html')
532 self.end_headers()
533 self.wfile.write('<html><head><style>'
534 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
535 '</style></head><body>'
536 '<div style="float: right">'
537 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
538 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000539
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000540 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000541 length = int(self.headers.getheader('content-length'))
542 qs = self.rfile.read(length)
543 params = cgi.parse_qs(qs, keep_blank_values=1)
544
545 for param in params:
546 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000547
548 self.wfile.write('</pre>')
549
550 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
551
552 self.wfile.write('</body></html>')
553 return True
554
555 def DownloadHandler(self):
556 """This handler sends a downloadable file with or without reporting
557 the size (6K)."""
558
559 if self.path.startswith("/download-unknown-size"):
560 send_length = False
561 elif self.path.startswith("/download-known-size"):
562 send_length = True
563 else:
564 return False
565
566 #
567 # The test which uses this functionality is attempting to send
568 # small chunks of data to the client. Use a fairly large buffer
569 # so that we'll fill chrome's IO buffer enough to force it to
570 # actually write the data.
571 # See also the comments in the client-side of this test in
572 # download_uitest.cc
573 #
574 size_chunk1 = 35*1024
575 size_chunk2 = 10*1024
576
577 self.send_response(200)
578 self.send_header('Content-type', 'application/octet-stream')
579 self.send_header('Cache-Control', 'max-age=0')
580 if send_length:
581 self.send_header('Content-Length', size_chunk1 + size_chunk2)
582 self.end_headers()
583
584 # First chunk of data:
585 self.wfile.write("*" * size_chunk1)
586 self.wfile.flush()
587
588 # handle requests until one of them clears this flag.
589 self.server.waitForDownload = True
590 while self.server.waitForDownload:
591 self.server.handle_request()
592
593 # Second chunk of data:
594 self.wfile.write("*" * size_chunk2)
595 return True
596
597 def DownloadFinishHandler(self):
598 """This handler just tells the server to finish the current download."""
599
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000600 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000601 return False
602
603 self.server.waitForDownload = False
604 self.send_response(200)
605 self.send_header('Content-type', 'text/html')
606 self.send_header('Cache-Control', 'max-age=0')
607 self.end_headers()
608 return True
609
610 def FileHandler(self):
611 """This handler sends the contents of the requested file. Wow, it's like
612 a real webserver!"""
613
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000614 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000615 if not self.path.startswith(prefix):
616 return False
617
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000618 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000619 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000620 self.rfile.read(int(self.headers.getheader('content-length')))
621
initial.commit94958cf2008-07-26 22:42:52 +0000622 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000623 if file.find('?') > -1:
624 # Ignore the query parameters entirely.
625 url, querystring = file.split('?')
626 else:
627 url = file
628 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000629 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000630 if os.path.isdir(path):
631 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000632
633 if not os.path.isfile(path):
634 print "File not found " + file + " full path:" + path
635 self.send_error(404)
636 return True
637
638 f = open(path, "rb")
639 data = f.read()
640 f.close()
641
642 # If file.mock-http-headers exists, it contains the headers we
643 # should send. Read them in and parse them.
644 headers_path = path + '.mock-http-headers'
645 if os.path.isfile(headers_path):
646 f = open(headers_path, "r")
647
648 # "HTTP/1.1 200 OK"
649 response = f.readline()
650 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
651 self.send_response(int(status_code))
652
653 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000654 header_values = re.findall('(\S+):\s*(.*)', line)
655 if len(header_values) > 0:
656 # "name: value"
657 name, value = header_values[0]
658 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000659 f.close()
660 else:
661 # Could be more generic once we support mime-type sniffing, but for
662 # now we need to set it explicitly.
663 self.send_response(200)
664 self.send_header('Content-type', self.GetMIMETypeFromName(file))
665 self.send_header('Content-Length', len(data))
666 self.end_headers()
667
668 self.wfile.write(data)
669
670 return True
671
672 def RealFileWithCommonHeaderHandler(self):
673 """This handler sends the contents of the requested file without the pseudo
674 http head!"""
675
676 prefix='/realfiles/'
677 if not self.path.startswith(prefix):
678 return False
679
680 file = self.path[len(prefix):]
681 path = os.path.join(self.server.data_dir, file)
682
683 try:
684 f = open(path, "rb")
685 data = f.read()
686 f.close()
687
688 # just simply set the MIME as octal stream
689 self.send_response(200)
690 self.send_header('Content-type', 'application/octet-stream')
691 self.end_headers()
692
693 self.wfile.write(data)
694 except:
695 self.send_error(404)
696
697 return True
698
699 def RealBZ2FileWithCommonHeaderHandler(self):
700 """This handler sends the bzip2 contents of the requested file with
701 corresponding Content-Encoding field in http head!"""
702
703 prefix='/realbz2files/'
704 if not self.path.startswith(prefix):
705 return False
706
707 parts = self.path.split('?')
708 file = parts[0][len(prefix):]
709 path = os.path.join(self.server.data_dir, file) + '.bz2'
710
711 if len(parts) > 1:
712 options = parts[1]
713 else:
714 options = ''
715
716 try:
717 self.send_response(200)
718 accept_encoding = self.headers.get("Accept-Encoding")
719 if accept_encoding.find("bzip2") != -1:
720 f = open(path, "rb")
721 data = f.read()
722 f.close()
723 self.send_header('Content-Encoding', 'bzip2')
724 self.send_header('Content-type', 'application/x-bzip2')
725 self.end_headers()
726 if options == 'incremental-header':
727 self.wfile.write(data[:1])
728 self.wfile.flush()
729 time.sleep(1.0)
730 self.wfile.write(data[1:])
731 else:
732 self.wfile.write(data)
733 else:
734 """client do not support bzip2 format, send pseudo content
735 """
736 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
737 self.end_headers()
738 self.wfile.write("you do not support bzip2 encoding")
739 except:
740 self.send_error(404)
741
742 return True
743
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000744 def SetCookieHandler(self):
745 """This handler just sets a cookie, for testing cookie handling."""
746
747 if not self._ShouldHandleRequest("/set-cookie"):
748 return False
749
750 query_char = self.path.find('?')
751 if query_char != -1:
752 cookie_values = self.path[query_char + 1:].split('&')
753 else:
754 cookie_values = ("",)
755 self.send_response(200)
756 self.send_header('Content-type', 'text/html')
757 for cookie_value in cookie_values:
758 self.send_header('Set-Cookie', '%s' % cookie_value)
759 self.end_headers()
760 for cookie_value in cookie_values:
761 self.wfile.write('%s' % cookie_value)
762 return True
763
initial.commit94958cf2008-07-26 22:42:52 +0000764 def AuthBasicHandler(self):
765 """This handler tests 'Basic' authentication. It just sends a page with
766 title 'user/pass' if you succeed."""
767
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000768 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000769 return False
770
771 username = userpass = password = b64str = ""
772
ericroman@google.com239b4d82009-03-27 04:00:22 +0000773 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
774
initial.commit94958cf2008-07-26 22:42:52 +0000775 auth = self.headers.getheader('authorization')
776 try:
777 if not auth:
778 raise Exception('no auth')
779 b64str = re.findall(r'Basic (\S+)', auth)[0]
780 userpass = base64.b64decode(b64str)
781 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
782 if password != 'secret':
783 raise Exception('wrong password')
784 except Exception, e:
785 # Authentication failed.
786 self.send_response(401)
787 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
788 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000789 if set_cookie_if_challenged:
790 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000791 self.end_headers()
792 self.wfile.write('<html><head>')
793 self.wfile.write('<title>Denied: %s</title>' % e)
794 self.wfile.write('</head><body>')
795 self.wfile.write('auth=%s<p>' % auth)
796 self.wfile.write('b64str=%s<p>' % b64str)
797 self.wfile.write('username: %s<p>' % username)
798 self.wfile.write('userpass: %s<p>' % userpass)
799 self.wfile.write('password: %s<p>' % password)
800 self.wfile.write('You sent:<br>%s<p>' % self.headers)
801 self.wfile.write('</body></html>')
802 return True
803
804 # Authentication successful. (Return a cachable response to allow for
805 # testing cached pages that require authentication.)
806 if_none_match = self.headers.getheader('if-none-match')
807 if if_none_match == "abc":
808 self.send_response(304)
809 self.end_headers()
810 else:
811 self.send_response(200)
812 self.send_header('Content-type', 'text/html')
813 self.send_header('Cache-control', 'max-age=60000')
814 self.send_header('Etag', 'abc')
815 self.end_headers()
816 self.wfile.write('<html><head>')
817 self.wfile.write('<title>%s/%s</title>' % (username, password))
818 self.wfile.write('</head><body>')
819 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000820 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000821 self.wfile.write('</body></html>')
822
823 return True
824
tonyg@chromium.org75054202010-03-31 22:06:10 +0000825 def GetNonce(self, force_reset=False):
826 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000827
tonyg@chromium.org75054202010-03-31 22:06:10 +0000828 This is a fake implementation. A real implementation would only use a given
829 nonce a single time (hence the name n-once). However, for the purposes of
830 unittesting, we don't care about the security of the nonce.
831
832 Args:
833 force_reset: Iff set, the nonce will be changed. Useful for testing the
834 "stale" response.
835 """
836 if force_reset or not self.server.nonce_time:
837 self.server.nonce_time = time.time()
838 return _new_md5('privatekey%s%d' %
839 (self.path, self.server.nonce_time)).hexdigest()
840
841 def AuthDigestHandler(self):
842 """This handler tests 'Digest' authentication.
843
844 It just sends a page with title 'user/pass' if you succeed.
845
846 A stale response is sent iff "stale" is present in the request path.
847 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000848 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000849 return False
850
tonyg@chromium.org75054202010-03-31 22:06:10 +0000851 stale = 'stale' in self.path
852 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000853 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000854 password = 'secret'
855 realm = 'testrealm'
856
857 auth = self.headers.getheader('authorization')
858 pairs = {}
859 try:
860 if not auth:
861 raise Exception('no auth')
862 if not auth.startswith('Digest'):
863 raise Exception('not digest')
864 # Pull out all the name="value" pairs as a dictionary.
865 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
866
867 # Make sure it's all valid.
868 if pairs['nonce'] != nonce:
869 raise Exception('wrong nonce')
870 if pairs['opaque'] != opaque:
871 raise Exception('wrong opaque')
872
873 # Check the 'response' value and make sure it matches our magic hash.
874 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000875 hash_a1 = _new_md5(
876 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000877 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000878 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000879 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000880 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
881 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000882 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000883
884 if pairs['response'] != response:
885 raise Exception('wrong password')
886 except Exception, e:
887 # Authentication failed.
888 self.send_response(401)
889 hdr = ('Digest '
890 'realm="%s", '
891 'domain="/", '
892 'qop="auth", '
893 'algorithm=MD5, '
894 'nonce="%s", '
895 'opaque="%s"') % (realm, nonce, opaque)
896 if stale:
897 hdr += ', stale="TRUE"'
898 self.send_header('WWW-Authenticate', hdr)
899 self.send_header('Content-type', 'text/html')
900 self.end_headers()
901 self.wfile.write('<html><head>')
902 self.wfile.write('<title>Denied: %s</title>' % e)
903 self.wfile.write('</head><body>')
904 self.wfile.write('auth=%s<p>' % auth)
905 self.wfile.write('pairs=%s<p>' % pairs)
906 self.wfile.write('You sent:<br>%s<p>' % self.headers)
907 self.wfile.write('We are replying:<br>%s<p>' % hdr)
908 self.wfile.write('</body></html>')
909 return True
910
911 # Authentication successful.
912 self.send_response(200)
913 self.send_header('Content-type', 'text/html')
914 self.end_headers()
915 self.wfile.write('<html><head>')
916 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
917 self.wfile.write('</head><body>')
918 self.wfile.write('auth=%s<p>' % auth)
919 self.wfile.write('pairs=%s<p>' % pairs)
920 self.wfile.write('</body></html>')
921
922 return True
923
924 def SlowServerHandler(self):
925 """Wait for the user suggested time before responding. The syntax is
926 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000927 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000928 return False
929 query_char = self.path.find('?')
930 wait_sec = 1.0
931 if query_char >= 0:
932 try:
933 wait_sec = int(self.path[query_char + 1:])
934 except ValueError:
935 pass
936 time.sleep(wait_sec)
937 self.send_response(200)
938 self.send_header('Content-type', 'text/plain')
939 self.end_headers()
940 self.wfile.write("waited %d seconds" % wait_sec)
941 return True
942
943 def ContentTypeHandler(self):
944 """Returns a string of html with the given content type. E.g.,
945 /contenttype?text/css returns an html file with the Content-Type
946 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000947 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000948 return False
949 query_char = self.path.find('?')
950 content_type = self.path[query_char + 1:].strip()
951 if not content_type:
952 content_type = 'text/html'
953 self.send_response(200)
954 self.send_header('Content-Type', content_type)
955 self.end_headers()
956 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
957 return True
958
959 def ServerRedirectHandler(self):
960 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000961 '/server-redirect?http://foo.bar/asdf' to redirect to
962 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000963
964 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000965 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000966 return False
967
968 query_char = self.path.find('?')
969 if query_char < 0 or len(self.path) <= query_char + 1:
970 self.sendRedirectHelp(test_name)
971 return True
972 dest = self.path[query_char + 1:]
973
974 self.send_response(301) # moved permanently
975 self.send_header('Location', dest)
976 self.send_header('Content-type', 'text/html')
977 self.end_headers()
978 self.wfile.write('<html><head>')
979 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
980
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000981 return True
initial.commit94958cf2008-07-26 22:42:52 +0000982
983 def ClientRedirectHandler(self):
984 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000985 '/client-redirect?http://foo.bar/asdf' to redirect to
986 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000987
988 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000989 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000990 return False
991
992 query_char = self.path.find('?');
993 if query_char < 0 or len(self.path) <= query_char + 1:
994 self.sendRedirectHelp(test_name)
995 return True
996 dest = self.path[query_char + 1:]
997
998 self.send_response(200)
999 self.send_header('Content-type', 'text/html')
1000 self.end_headers()
1001 self.wfile.write('<html><head>')
1002 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1003 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1004
1005 return True
1006
nick@chromium.org2d78c222010-04-30 05:37:51 +00001007 def ChromiumSyncTimeHandler(self):
1008 """Handle Chromium sync .../time requests.
1009
1010 The syncer sometimes checks server reachability by examining /time.
1011 """
1012 test_name = "/chromiumsync/time"
1013 if not self._ShouldHandleRequest(test_name):
1014 return False
1015
1016 self.send_response(200)
1017 self.send_header('Content-type', 'text/html')
1018 self.end_headers()
1019 return True
1020
1021 def ChromiumSyncCommandHandler(self):
1022 """Handle a chromiumsync command arriving via http.
1023
1024 This covers all sync protocol commands: authentication, getupdates, and
1025 commit.
1026 """
1027 test_name = "/chromiumsync/command"
1028 if not self._ShouldHandleRequest(test_name):
1029 return False
1030
1031 length = int(self.headers.getheader('content-length'))
1032 raw_request = self.rfile.read(length)
1033
1034 http_response, raw_reply = self._sync_handler.HandleCommand(raw_request)
1035 self.send_response(http_response)
1036 self.end_headers()
1037 self.wfile.write(raw_reply)
1038 return True
1039
tony@chromium.org03266982010-03-05 03:18:42 +00001040 def MultipartHandler(self):
1041 """Send a multipart response (10 text/html pages)."""
1042 test_name = "/multipart"
1043 if not self._ShouldHandleRequest(test_name):
1044 return False
1045
1046 num_frames = 10
1047 bound = '12345'
1048 self.send_response(200)
1049 self.send_header('Content-type',
1050 'multipart/x-mixed-replace;boundary=' + bound)
1051 self.end_headers()
1052
1053 for i in xrange(num_frames):
1054 self.wfile.write('--' + bound + '\r\n')
1055 self.wfile.write('Content-type: text/html\r\n\r\n')
1056 self.wfile.write('<title>page ' + str(i) + '</title>')
1057 self.wfile.write('page ' + str(i))
1058
1059 self.wfile.write('--' + bound + '--')
1060 return True
1061
initial.commit94958cf2008-07-26 22:42:52 +00001062 def DefaultResponseHandler(self):
1063 """This is the catch-all response handler for requests that aren't handled
1064 by one of the special handlers above.
1065 Note that we specify the content-length as without it the https connection
1066 is not closed properly (and the browser keeps expecting data)."""
1067
1068 contents = "Default response given for path: " + self.path
1069 self.send_response(200)
1070 self.send_header('Content-type', 'text/html')
1071 self.send_header("Content-Length", len(contents))
1072 self.end_headers()
1073 self.wfile.write(contents)
1074 return True
1075
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001076 def RedirectConnectHandler(self):
1077 """Sends a redirect to the CONNECT request for www.redirect.com. This
1078 response is not specified by the RFC, so the browser should not follow
1079 the redirect."""
1080
1081 if (self.path.find("www.redirect.com") < 0):
1082 return False
1083
1084 dest = "http://www.destination.com/foo.js"
1085
1086 self.send_response(302) # moved temporarily
1087 self.send_header('Location', dest)
1088 self.send_header('Connection', 'close')
1089 self.end_headers()
1090 return True
1091
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001092 def ServerAuthConnectHandler(self):
1093 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1094 response doesn't make sense because the proxy server cannot request
1095 server authentication."""
1096
1097 if (self.path.find("www.server-auth.com") < 0):
1098 return False
1099
1100 challenge = 'Basic realm="WallyWorld"'
1101
1102 self.send_response(401) # unauthorized
1103 self.send_header('WWW-Authenticate', challenge)
1104 self.send_header('Connection', 'close')
1105 self.end_headers()
1106 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001107
1108 def DefaultConnectResponseHandler(self):
1109 """This is the catch-all response handler for CONNECT requests that aren't
1110 handled by one of the special handlers above. Real Web servers respond
1111 with 400 to CONNECT requests."""
1112
1113 contents = "Your client has issued a malformed or illegal request."
1114 self.send_response(400) # bad request
1115 self.send_header('Content-type', 'text/html')
1116 self.send_header("Content-Length", len(contents))
1117 self.end_headers()
1118 self.wfile.write(contents)
1119 return True
1120
1121 def do_CONNECT(self):
1122 for handler in self._connect_handlers:
1123 if handler():
1124 return
1125
initial.commit94958cf2008-07-26 22:42:52 +00001126 def do_GET(self):
1127 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001128 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001129 return
1130
1131 def do_POST(self):
1132 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001133 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001134 return
1135
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001136 def do_PUT(self):
1137 for handler in self._put_handlers:
1138 if handler():
1139 return
1140
initial.commit94958cf2008-07-26 22:42:52 +00001141 # called by the redirect handling function when there is no parameter
1142 def sendRedirectHelp(self, redirect_name):
1143 self.send_response(200)
1144 self.send_header('Content-type', 'text/html')
1145 self.end_headers()
1146 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1147 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1148 self.wfile.write('</body></html>')
1149
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001150def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001151 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1152 requests will be stored. If the directory already exists all files and
1153 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001154 dump_dir = os.path.join(data_dir, 'dump');
1155 if os.path.isdir(dump_dir):
1156 shutil.rmtree(dump_dir)
1157 os.mkdir(dump_dir)
1158
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001159def MakeDataDir():
1160 if options.data_dir:
1161 if not os.path.isdir(options.data_dir):
1162 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1163 return None
1164 my_data_dir = options.data_dir
1165 else:
1166 # Create the default path to our data dir, relative to the exe dir.
1167 my_data_dir = os.path.dirname(sys.argv[0])
1168 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
nick@chromium.org2d78c222010-04-30 05:37:51 +00001169 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001170
1171 #TODO(ibrar): Must use Find* funtion defined in google\tools
1172 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1173
1174 return my_data_dir
1175
nick@chromium.org2d78c222010-04-30 05:37:51 +00001176def TryKillingOldServer(port):
1177 # Note that an HTTP /kill request to the FTP server has the effect of
1178 # killing it.
1179 for protocol in ["http", "https"]:
1180 try:
1181 urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
1182 print "Killed old server instance on port %d (via %s)" % (port, protocol)
1183 except urllib2.URLError:
1184 # Common case, indicates no server running.
1185 pass
1186
initial.commit94958cf2008-07-26 22:42:52 +00001187def main(options, args):
1188 # redirect output to a log file so it doesn't spam the unit test output
1189 logfile = open('testserver.log', 'w')
1190 sys.stderr = sys.stdout = logfile
1191
1192 port = options.port
1193
nick@chromium.org2d78c222010-04-30 05:37:51 +00001194 # Try to free up the port if there's an orphaned old instance.
1195 TryKillingOldServer(port)
1196
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001197 if options.server_type == SERVER_HTTP:
1198 if options.cert:
1199 # let's make sure the cert file exists.
1200 if not os.path.isfile(options.cert):
1201 print 'specified cert file not found: ' + options.cert + ' exiting...'
1202 return
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001203 if options.forking:
1204 server_class = ForkingHTTPSServer
1205 else:
1206 server_class = HTTPSServer
1207 server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001208 print 'HTTPS server started on port %d...' % port
1209 else:
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001210 if options.forking:
1211 server_class = ForkingHTTPServer
1212 else:
1213 server_class = StoppableHTTPServer
1214 server = server_class(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001215 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001216
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001217 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001218 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001219 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001220
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001221 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001222 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001223 my_data_dir = MakeDataDir()
1224
1225 def line_logger(msg):
1226 if (msg.find("kill") >= 0):
1227 server.stop = True
1228 print 'shutting down server'
1229 sys.exit(0)
1230
1231 # Instantiate a dummy authorizer for managing 'virtual' users
1232 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1233
1234 # Define a new user having full r/w permissions and a read-only
1235 # anonymous user
1236 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1237
1238 authorizer.add_anonymous(my_data_dir)
1239
1240 # Instantiate FTP handler class
1241 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1242 ftp_handler.authorizer = authorizer
1243 pyftpdlib.ftpserver.logline = line_logger
1244
1245 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001246 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1247 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001248
1249 # Instantiate FTP server class and listen to 127.0.0.1:port
1250 address = ('127.0.0.1', port)
1251 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1252 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001253
1254 try:
1255 server.serve_forever()
1256 except KeyboardInterrupt:
1257 print 'shutting down server'
1258 server.stop = True
1259
1260if __name__ == '__main__':
1261 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001262 option_parser.add_option("-f", '--ftp', action='store_const',
1263 const=SERVER_FTP, default=SERVER_HTTP,
1264 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001265 help='FTP or HTTP server: default is HTTP.')
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001266 option_parser.add_option('--forking', action='store_true', default=False,
1267 dest='forking',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001268 help='Serve each request in a separate process.')
initial.commit94958cf2008-07-26 22:42:52 +00001269 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001270 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001271 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001272 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001273 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001274 help='Specify that https should be used, specify '
1275 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001276 'the server should use.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001277 option_parser.add_option('', '--file-root-url', default='/files/',
1278 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001279 option_parser.add_option('', '--never-die', default=False,
1280 action="store_true",
1281 help='Prevent the server from dying when visiting '
1282 'a /kill URL. Useful for manually running some '
1283 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001284 options, args = option_parser.parse_args()
1285
1286 sys.exit(main(options, args))