blob: 931d846a13a491cb30889951a7c1840737f5b631 [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
license.botf3378c22008-08-24 00:55:55 +00002# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
3# 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
25import tlslite
26import tlslite.api
erikkay@google.comd5182ff2009-01-08 20:45:27 +000027import pyftpdlib.ftpserver
28
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000029try:
30 import hashlib
31 _new_md5 = hashlib.md5
32except ImportError:
33 import md5
34 _new_md5 = md5.new
35
maruel@chromium.org756cf982009-03-05 12:46:38 +000036SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000037SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000038
39debug_output = sys.stderr
40def debug(str):
41 debug_output.write(str + "\n")
42 debug_output.flush()
43
44class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
45 """This is a specialization of of BaseHTTPServer to allow it
46 to be exited cleanly (by setting its "stop" member to True)."""
47
48 def serve_forever(self):
49 self.stop = False
50 self.nonce = None
51 while not self.stop:
52 self.handle_request()
53 self.socket.close()
54
55class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
56 """This is a specialization of StoppableHTTPerver that add https support."""
57
58 def __init__(self, server_address, request_hander_class, cert_path):
59 s = open(cert_path).read()
60 x509 = tlslite.api.X509()
61 x509.parse(s)
62 self.cert_chain = tlslite.api.X509CertChain([x509])
63 s = open(cert_path).read()
64 self.private_key = tlslite.api.parsePEMKey(s, private=True)
65
66 self.session_cache = tlslite.api.SessionCache()
67 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
68
69 def handshake(self, tlsConnection):
70 """Creates the SSL connection."""
71 try:
72 tlsConnection.handshakeServer(certChain=self.cert_chain,
73 privateKey=self.private_key,
74 sessionCache=self.session_cache)
75 tlsConnection.ignoreAbruptClose = True
76 return True
77 except tlslite.api.TLSError, error:
78 print "Handshake failure:", str(error)
79 return False
80
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +000081class ForkingHTTPServer(SocketServer.ForkingMixIn, StoppableHTTPServer):
82 """This is a specialization of of StoppableHTTPServer which serves each
83 request in a separate process"""
84 pass
85
86class ForkingHTTPSServer(SocketServer.ForkingMixIn, HTTPSServer):
87 """This is a specialization of of HTTPSServer which serves each
88 request in a separate process"""
89 pass
90
initial.commit94958cf2008-07-26 22:42:52 +000091class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
92
93 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000094 self._connect_handlers = [
95 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000096 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000097 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +000098 self._get_handlers = [
99 self.KillHandler,
100 self.NoCacheMaxAgeTimeHandler,
101 self.NoCacheTimeHandler,
102 self.CacheTimeHandler,
103 self.CacheExpiresHandler,
104 self.CacheProxyRevalidateHandler,
105 self.CachePrivateHandler,
106 self.CachePublicHandler,
107 self.CacheSMaxAgeHandler,
108 self.CacheMustRevalidateHandler,
109 self.CacheMustRevalidateMaxAgeHandler,
110 self.CacheNoStoreHandler,
111 self.CacheNoStoreMaxAgeHandler,
112 self.CacheNoTransformHandler,
113 self.DownloadHandler,
114 self.DownloadFinishHandler,
115 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000116 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000117 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000118 self.FileHandler,
119 self.RealFileWithCommonHeaderHandler,
120 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000121 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000122 self.AuthBasicHandler,
123 self.AuthDigestHandler,
124 self.SlowServerHandler,
125 self.ContentTypeHandler,
126 self.ServerRedirectHandler,
127 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000128 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000129 self.DefaultResponseHandler]
130 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000131 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000132 self.EchoTitleHandler,
133 self.EchoAllHandler,
134 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000135 self._put_handlers = [
136 self.WriteFile,
137 self.EchoTitleHandler,
138 self.EchoAllHandler,
139 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000140
maruel@google.come250a9b2009-03-10 17:39:46 +0000141 self._mime_types = {
142 'gif': 'image/gif',
143 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000144 'jpg' : 'image/jpeg',
145 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000146 }
initial.commit94958cf2008-07-26 22:42:52 +0000147 self._default_mime_type = 'text/html'
148
maruel@google.come250a9b2009-03-10 17:39:46 +0000149 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
150 client_address,
151 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000152
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000153 def _ShouldHandleRequest(self, handler_name):
154 """Determines if the path can be handled by the handler.
155
156 We consider a handler valid if the path begins with the
157 handler name. It can optionally be followed by "?*", "/*".
158 """
159
160 pattern = re.compile('%s($|\?|/).*' % handler_name)
161 return pattern.match(self.path)
162
initial.commit94958cf2008-07-26 22:42:52 +0000163 def GetMIMETypeFromName(self, file_name):
164 """Returns the mime type for the specified file_name. So far it only looks
165 at the file extension."""
166
167 (shortname, extension) = os.path.splitext(file_name)
168 if len(extension) == 0:
169 # no extension.
170 return self._default_mime_type
171
ericroman@google.comc17ca532009-05-07 03:51:05 +0000172 # extension starts with a dot, so we need to remove it
173 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000174
175 def KillHandler(self):
176 """This request handler kills the server, for use when we're done"
177 with the a particular test."""
178
179 if (self.path.find("kill") < 0):
180 return False
181
182 self.send_response(200)
183 self.send_header('Content-type', 'text/html')
184 self.send_header('Cache-Control', 'max-age=0')
185 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000186 if options.never_die:
187 self.wfile.write('I cannot die!! BWAHAHA')
188 else:
189 self.wfile.write('Goodbye cruel world!')
190 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000191
192 return True
193
194 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
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000473 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000474 """This is handler dumps the content of POST/PUT request to a disk file
475 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000476
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000477 prefix='/writefile/'
478 if not self.path.startswith(prefix):
479 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000480
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000481 file_name = self.path[len(prefix):]
482
483 # do not allow fancy chars in file name
484 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
485 if len(file_name) and file_name[0] != '.':
486 path = os.path.join(self.server.data_dir, 'dump', file_name);
487 length = int(self.headers.getheader('content-length'))
488 request = self.rfile.read(length)
489 f = open(path, "wb")
490 f.write(request);
491 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000492
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000493 self.send_response(200)
494 self.send_header('Content-type', 'text/html')
495 self.end_headers()
496 self.wfile.write('<html>%s</html>' % file_name)
497 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000498
initial.commit94958cf2008-07-26 22:42:52 +0000499 def EchoTitleHandler(self):
500 """This handler is like Echo, but sets the page title to the request."""
501
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000502 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000503 return False
504
505 self.send_response(200)
506 self.send_header('Content-type', 'text/html')
507 self.end_headers()
508 length = int(self.headers.getheader('content-length'))
509 request = self.rfile.read(length)
510 self.wfile.write('<html><head><title>')
511 self.wfile.write(request)
512 self.wfile.write('</title></head></html>')
513 return True
514
515 def EchoAllHandler(self):
516 """This handler yields a (more) human-readable page listing information
517 about the request header & contents."""
518
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000519 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000520 return False
521
522 self.send_response(200)
523 self.send_header('Content-type', 'text/html')
524 self.end_headers()
525 self.wfile.write('<html><head><style>'
526 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
527 '</style></head><body>'
528 '<div style="float: right">'
529 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
530 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000531
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000532 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000533 length = int(self.headers.getheader('content-length'))
534 qs = self.rfile.read(length)
535 params = cgi.parse_qs(qs, keep_blank_values=1)
536
537 for param in params:
538 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000539
540 self.wfile.write('</pre>')
541
542 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
543
544 self.wfile.write('</body></html>')
545 return True
546
547 def DownloadHandler(self):
548 """This handler sends a downloadable file with or without reporting
549 the size (6K)."""
550
551 if self.path.startswith("/download-unknown-size"):
552 send_length = False
553 elif self.path.startswith("/download-known-size"):
554 send_length = True
555 else:
556 return False
557
558 #
559 # The test which uses this functionality is attempting to send
560 # small chunks of data to the client. Use a fairly large buffer
561 # so that we'll fill chrome's IO buffer enough to force it to
562 # actually write the data.
563 # See also the comments in the client-side of this test in
564 # download_uitest.cc
565 #
566 size_chunk1 = 35*1024
567 size_chunk2 = 10*1024
568
569 self.send_response(200)
570 self.send_header('Content-type', 'application/octet-stream')
571 self.send_header('Cache-Control', 'max-age=0')
572 if send_length:
573 self.send_header('Content-Length', size_chunk1 + size_chunk2)
574 self.end_headers()
575
576 # First chunk of data:
577 self.wfile.write("*" * size_chunk1)
578 self.wfile.flush()
579
580 # handle requests until one of them clears this flag.
581 self.server.waitForDownload = True
582 while self.server.waitForDownload:
583 self.server.handle_request()
584
585 # Second chunk of data:
586 self.wfile.write("*" * size_chunk2)
587 return True
588
589 def DownloadFinishHandler(self):
590 """This handler just tells the server to finish the current download."""
591
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000592 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000593 return False
594
595 self.server.waitForDownload = False
596 self.send_response(200)
597 self.send_header('Content-type', 'text/html')
598 self.send_header('Cache-Control', 'max-age=0')
599 self.end_headers()
600 return True
601
602 def FileHandler(self):
603 """This handler sends the contents of the requested file. Wow, it's like
604 a real webserver!"""
605
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000606 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000607 if not self.path.startswith(prefix):
608 return False
609
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000610 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000611 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000612 self.rfile.read(int(self.headers.getheader('content-length')))
613
initial.commit94958cf2008-07-26 22:42:52 +0000614 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000615 if file.find('?') > -1:
616 # Ignore the query parameters entirely.
617 url, querystring = file.split('?')
618 else:
619 url = file
620 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000621 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000622 if os.path.isdir(path):
623 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000624
625 if not os.path.isfile(path):
626 print "File not found " + file + " full path:" + path
627 self.send_error(404)
628 return True
629
630 f = open(path, "rb")
631 data = f.read()
632 f.close()
633
634 # If file.mock-http-headers exists, it contains the headers we
635 # should send. Read them in and parse them.
636 headers_path = path + '.mock-http-headers'
637 if os.path.isfile(headers_path):
638 f = open(headers_path, "r")
639
640 # "HTTP/1.1 200 OK"
641 response = f.readline()
642 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
643 self.send_response(int(status_code))
644
645 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000646 header_values = re.findall('(\S+):\s*(.*)', line)
647 if len(header_values) > 0:
648 # "name: value"
649 name, value = header_values[0]
650 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000651 f.close()
652 else:
653 # Could be more generic once we support mime-type sniffing, but for
654 # now we need to set it explicitly.
655 self.send_response(200)
656 self.send_header('Content-type', self.GetMIMETypeFromName(file))
657 self.send_header('Content-Length', len(data))
658 self.end_headers()
659
660 self.wfile.write(data)
661
662 return True
663
664 def RealFileWithCommonHeaderHandler(self):
665 """This handler sends the contents of the requested file without the pseudo
666 http head!"""
667
668 prefix='/realfiles/'
669 if not self.path.startswith(prefix):
670 return False
671
672 file = self.path[len(prefix):]
673 path = os.path.join(self.server.data_dir, file)
674
675 try:
676 f = open(path, "rb")
677 data = f.read()
678 f.close()
679
680 # just simply set the MIME as octal stream
681 self.send_response(200)
682 self.send_header('Content-type', 'application/octet-stream')
683 self.end_headers()
684
685 self.wfile.write(data)
686 except:
687 self.send_error(404)
688
689 return True
690
691 def RealBZ2FileWithCommonHeaderHandler(self):
692 """This handler sends the bzip2 contents of the requested file with
693 corresponding Content-Encoding field in http head!"""
694
695 prefix='/realbz2files/'
696 if not self.path.startswith(prefix):
697 return False
698
699 parts = self.path.split('?')
700 file = parts[0][len(prefix):]
701 path = os.path.join(self.server.data_dir, file) + '.bz2'
702
703 if len(parts) > 1:
704 options = parts[1]
705 else:
706 options = ''
707
708 try:
709 self.send_response(200)
710 accept_encoding = self.headers.get("Accept-Encoding")
711 if accept_encoding.find("bzip2") != -1:
712 f = open(path, "rb")
713 data = f.read()
714 f.close()
715 self.send_header('Content-Encoding', 'bzip2')
716 self.send_header('Content-type', 'application/x-bzip2')
717 self.end_headers()
718 if options == 'incremental-header':
719 self.wfile.write(data[:1])
720 self.wfile.flush()
721 time.sleep(1.0)
722 self.wfile.write(data[1:])
723 else:
724 self.wfile.write(data)
725 else:
726 """client do not support bzip2 format, send pseudo content
727 """
728 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
729 self.end_headers()
730 self.wfile.write("you do not support bzip2 encoding")
731 except:
732 self.send_error(404)
733
734 return True
735
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000736 def SetCookieHandler(self):
737 """This handler just sets a cookie, for testing cookie handling."""
738
739 if not self._ShouldHandleRequest("/set-cookie"):
740 return False
741
742 query_char = self.path.find('?')
743 if query_char != -1:
744 cookie_values = self.path[query_char + 1:].split('&')
745 else:
746 cookie_values = ("",)
747 self.send_response(200)
748 self.send_header('Content-type', 'text/html')
749 for cookie_value in cookie_values:
750 self.send_header('Set-Cookie', '%s' % cookie_value)
751 self.end_headers()
752 for cookie_value in cookie_values:
753 self.wfile.write('%s' % cookie_value)
754 return True
755
initial.commit94958cf2008-07-26 22:42:52 +0000756 def AuthBasicHandler(self):
757 """This handler tests 'Basic' authentication. It just sends a page with
758 title 'user/pass' if you succeed."""
759
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000760 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000761 return False
762
763 username = userpass = password = b64str = ""
764
ericroman@google.com239b4d82009-03-27 04:00:22 +0000765 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
766
initial.commit94958cf2008-07-26 22:42:52 +0000767 auth = self.headers.getheader('authorization')
768 try:
769 if not auth:
770 raise Exception('no auth')
771 b64str = re.findall(r'Basic (\S+)', auth)[0]
772 userpass = base64.b64decode(b64str)
773 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
774 if password != 'secret':
775 raise Exception('wrong password')
776 except Exception, e:
777 # Authentication failed.
778 self.send_response(401)
779 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
780 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000781 if set_cookie_if_challenged:
782 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000783 self.end_headers()
784 self.wfile.write('<html><head>')
785 self.wfile.write('<title>Denied: %s</title>' % e)
786 self.wfile.write('</head><body>')
787 self.wfile.write('auth=%s<p>' % auth)
788 self.wfile.write('b64str=%s<p>' % b64str)
789 self.wfile.write('username: %s<p>' % username)
790 self.wfile.write('userpass: %s<p>' % userpass)
791 self.wfile.write('password: %s<p>' % password)
792 self.wfile.write('You sent:<br>%s<p>' % self.headers)
793 self.wfile.write('</body></html>')
794 return True
795
796 # Authentication successful. (Return a cachable response to allow for
797 # testing cached pages that require authentication.)
798 if_none_match = self.headers.getheader('if-none-match')
799 if if_none_match == "abc":
800 self.send_response(304)
801 self.end_headers()
802 else:
803 self.send_response(200)
804 self.send_header('Content-type', 'text/html')
805 self.send_header('Cache-control', 'max-age=60000')
806 self.send_header('Etag', 'abc')
807 self.end_headers()
808 self.wfile.write('<html><head>')
809 self.wfile.write('<title>%s/%s</title>' % (username, password))
810 self.wfile.write('</head><body>')
811 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000812 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000813 self.wfile.write('</body></html>')
814
815 return True
816
817 def AuthDigestHandler(self):
818 """This handler tests 'Digest' authentication. It just sends a page with
819 title 'user/pass' if you succeed."""
820
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000821 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000822 return False
823
824 # Periodically generate a new nonce. Technically we should incorporate
825 # the request URL into this, but we don't care for testing.
826 nonce_life = 10
827 stale = False
maruel@google.come250a9b2009-03-10 17:39:46 +0000828 if (not self.server.nonce or
829 (time.time() - self.server.nonce_time > nonce_life)):
initial.commit94958cf2008-07-26 22:42:52 +0000830 if self.server.nonce:
831 stale = True
832 self.server.nonce_time = time.time()
833 self.server.nonce = \
maruel@google.come250a9b2009-03-10 17:39:46 +0000834 _new_md5(time.ctime(self.server.nonce_time) +
835 'privatekey').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000836
837 nonce = self.server.nonce
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000838 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000839 password = 'secret'
840 realm = 'testrealm'
841
842 auth = self.headers.getheader('authorization')
843 pairs = {}
844 try:
845 if not auth:
846 raise Exception('no auth')
847 if not auth.startswith('Digest'):
848 raise Exception('not digest')
849 # Pull out all the name="value" pairs as a dictionary.
850 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
851
852 # Make sure it's all valid.
853 if pairs['nonce'] != nonce:
854 raise Exception('wrong nonce')
855 if pairs['opaque'] != opaque:
856 raise Exception('wrong opaque')
857
858 # Check the 'response' value and make sure it matches our magic hash.
859 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000860 hash_a1 = _new_md5(
861 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000862 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000863 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000864 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000865 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
866 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000867 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000868
869 if pairs['response'] != response:
870 raise Exception('wrong password')
871 except Exception, e:
872 # Authentication failed.
873 self.send_response(401)
874 hdr = ('Digest '
875 'realm="%s", '
876 'domain="/", '
877 'qop="auth", '
878 'algorithm=MD5, '
879 'nonce="%s", '
880 'opaque="%s"') % (realm, nonce, opaque)
881 if stale:
882 hdr += ', stale="TRUE"'
883 self.send_header('WWW-Authenticate', hdr)
884 self.send_header('Content-type', 'text/html')
885 self.end_headers()
886 self.wfile.write('<html><head>')
887 self.wfile.write('<title>Denied: %s</title>' % e)
888 self.wfile.write('</head><body>')
889 self.wfile.write('auth=%s<p>' % auth)
890 self.wfile.write('pairs=%s<p>' % pairs)
891 self.wfile.write('You sent:<br>%s<p>' % self.headers)
892 self.wfile.write('We are replying:<br>%s<p>' % hdr)
893 self.wfile.write('</body></html>')
894 return True
895
896 # Authentication successful.
897 self.send_response(200)
898 self.send_header('Content-type', 'text/html')
899 self.end_headers()
900 self.wfile.write('<html><head>')
901 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
902 self.wfile.write('</head><body>')
903 self.wfile.write('auth=%s<p>' % auth)
904 self.wfile.write('pairs=%s<p>' % pairs)
905 self.wfile.write('</body></html>')
906
907 return True
908
909 def SlowServerHandler(self):
910 """Wait for the user suggested time before responding. The syntax is
911 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000912 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000913 return False
914 query_char = self.path.find('?')
915 wait_sec = 1.0
916 if query_char >= 0:
917 try:
918 wait_sec = int(self.path[query_char + 1:])
919 except ValueError:
920 pass
921 time.sleep(wait_sec)
922 self.send_response(200)
923 self.send_header('Content-type', 'text/plain')
924 self.end_headers()
925 self.wfile.write("waited %d seconds" % wait_sec)
926 return True
927
928 def ContentTypeHandler(self):
929 """Returns a string of html with the given content type. E.g.,
930 /contenttype?text/css returns an html file with the Content-Type
931 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000932 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000933 return False
934 query_char = self.path.find('?')
935 content_type = self.path[query_char + 1:].strip()
936 if not content_type:
937 content_type = 'text/html'
938 self.send_response(200)
939 self.send_header('Content-Type', content_type)
940 self.end_headers()
941 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
942 return True
943
944 def ServerRedirectHandler(self):
945 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000946 '/server-redirect?http://foo.bar/asdf' to redirect to
947 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000948
949 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000950 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000951 return False
952
953 query_char = self.path.find('?')
954 if query_char < 0 or len(self.path) <= query_char + 1:
955 self.sendRedirectHelp(test_name)
956 return True
957 dest = self.path[query_char + 1:]
958
959 self.send_response(301) # moved permanently
960 self.send_header('Location', dest)
961 self.send_header('Content-type', 'text/html')
962 self.end_headers()
963 self.wfile.write('<html><head>')
964 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
965
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000966 return True
initial.commit94958cf2008-07-26 22:42:52 +0000967
968 def ClientRedirectHandler(self):
969 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000970 '/client-redirect?http://foo.bar/asdf' to redirect to
971 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000972
973 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000974 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000975 return False
976
977 query_char = self.path.find('?');
978 if query_char < 0 or len(self.path) <= query_char + 1:
979 self.sendRedirectHelp(test_name)
980 return True
981 dest = self.path[query_char + 1:]
982
983 self.send_response(200)
984 self.send_header('Content-type', 'text/html')
985 self.end_headers()
986 self.wfile.write('<html><head>')
987 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
988 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
989
990 return True
991
tony@chromium.org03266982010-03-05 03:18:42 +0000992 def MultipartHandler(self):
993 """Send a multipart response (10 text/html pages)."""
994 test_name = "/multipart"
995 if not self._ShouldHandleRequest(test_name):
996 return False
997
998 num_frames = 10
999 bound = '12345'
1000 self.send_response(200)
1001 self.send_header('Content-type',
1002 'multipart/x-mixed-replace;boundary=' + bound)
1003 self.end_headers()
1004
1005 for i in xrange(num_frames):
1006 self.wfile.write('--' + bound + '\r\n')
1007 self.wfile.write('Content-type: text/html\r\n\r\n')
1008 self.wfile.write('<title>page ' + str(i) + '</title>')
1009 self.wfile.write('page ' + str(i))
1010
1011 self.wfile.write('--' + bound + '--')
1012 return True
1013
initial.commit94958cf2008-07-26 22:42:52 +00001014 def DefaultResponseHandler(self):
1015 """This is the catch-all response handler for requests that aren't handled
1016 by one of the special handlers above.
1017 Note that we specify the content-length as without it the https connection
1018 is not closed properly (and the browser keeps expecting data)."""
1019
1020 contents = "Default response given for path: " + self.path
1021 self.send_response(200)
1022 self.send_header('Content-type', 'text/html')
1023 self.send_header("Content-Length", len(contents))
1024 self.end_headers()
1025 self.wfile.write(contents)
1026 return True
1027
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001028 def RedirectConnectHandler(self):
1029 """Sends a redirect to the CONNECT request for www.redirect.com. This
1030 response is not specified by the RFC, so the browser should not follow
1031 the redirect."""
1032
1033 if (self.path.find("www.redirect.com") < 0):
1034 return False
1035
1036 dest = "http://www.destination.com/foo.js"
1037
1038 self.send_response(302) # moved temporarily
1039 self.send_header('Location', dest)
1040 self.send_header('Connection', 'close')
1041 self.end_headers()
1042 return True
1043
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001044 def ServerAuthConnectHandler(self):
1045 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1046 response doesn't make sense because the proxy server cannot request
1047 server authentication."""
1048
1049 if (self.path.find("www.server-auth.com") < 0):
1050 return False
1051
1052 challenge = 'Basic realm="WallyWorld"'
1053
1054 self.send_response(401) # unauthorized
1055 self.send_header('WWW-Authenticate', challenge)
1056 self.send_header('Connection', 'close')
1057 self.end_headers()
1058 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001059
1060 def DefaultConnectResponseHandler(self):
1061 """This is the catch-all response handler for CONNECT requests that aren't
1062 handled by one of the special handlers above. Real Web servers respond
1063 with 400 to CONNECT requests."""
1064
1065 contents = "Your client has issued a malformed or illegal request."
1066 self.send_response(400) # bad request
1067 self.send_header('Content-type', 'text/html')
1068 self.send_header("Content-Length", len(contents))
1069 self.end_headers()
1070 self.wfile.write(contents)
1071 return True
1072
1073 def do_CONNECT(self):
1074 for handler in self._connect_handlers:
1075 if handler():
1076 return
1077
initial.commit94958cf2008-07-26 22:42:52 +00001078 def do_GET(self):
1079 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001080 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001081 return
1082
1083 def do_POST(self):
1084 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001085 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001086 return
1087
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001088 def do_PUT(self):
1089 for handler in self._put_handlers:
1090 if handler():
1091 return
1092
initial.commit94958cf2008-07-26 22:42:52 +00001093 # called by the redirect handling function when there is no parameter
1094 def sendRedirectHelp(self, redirect_name):
1095 self.send_response(200)
1096 self.send_header('Content-type', 'text/html')
1097 self.end_headers()
1098 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1099 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1100 self.wfile.write('</body></html>')
1101
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001102def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001103 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1104 requests will be stored. If the directory already exists all files and
1105 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001106 dump_dir = os.path.join(data_dir, 'dump');
1107 if os.path.isdir(dump_dir):
1108 shutil.rmtree(dump_dir)
1109 os.mkdir(dump_dir)
1110
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001111def MakeDataDir():
1112 if options.data_dir:
1113 if not os.path.isdir(options.data_dir):
1114 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1115 return None
1116 my_data_dir = options.data_dir
1117 else:
1118 # Create the default path to our data dir, relative to the exe dir.
1119 my_data_dir = os.path.dirname(sys.argv[0])
1120 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1121 "test", "data")
1122
1123 #TODO(ibrar): Must use Find* funtion defined in google\tools
1124 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1125
1126 return my_data_dir
1127
initial.commit94958cf2008-07-26 22:42:52 +00001128def main(options, args):
1129 # redirect output to a log file so it doesn't spam the unit test output
1130 logfile = open('testserver.log', 'w')
1131 sys.stderr = sys.stdout = logfile
1132
1133 port = options.port
1134
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001135 if options.server_type == SERVER_HTTP:
1136 if options.cert:
1137 # let's make sure the cert file exists.
1138 if not os.path.isfile(options.cert):
1139 print 'specified cert file not found: ' + options.cert + ' exiting...'
1140 return
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001141 if options.forking:
1142 server_class = ForkingHTTPSServer
1143 else:
1144 server_class = HTTPSServer
1145 server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001146 print 'HTTPS server started on port %d...' % port
1147 else:
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001148 if options.forking:
1149 server_class = ForkingHTTPServer
1150 else:
1151 server_class = StoppableHTTPServer
1152 server = server_class(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001153 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001154
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001155 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001156 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001157 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001158
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001159 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001160 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001161 my_data_dir = MakeDataDir()
1162
1163 def line_logger(msg):
1164 if (msg.find("kill") >= 0):
1165 server.stop = True
1166 print 'shutting down server'
1167 sys.exit(0)
1168
1169 # Instantiate a dummy authorizer for managing 'virtual' users
1170 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1171
1172 # Define a new user having full r/w permissions and a read-only
1173 # anonymous user
1174 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1175
1176 authorizer.add_anonymous(my_data_dir)
1177
1178 # Instantiate FTP handler class
1179 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1180 ftp_handler.authorizer = authorizer
1181 pyftpdlib.ftpserver.logline = line_logger
1182
1183 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001184 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1185 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001186
1187 # Instantiate FTP server class and listen to 127.0.0.1:port
1188 address = ('127.0.0.1', port)
1189 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1190 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001191
1192 try:
1193 server.serve_forever()
1194 except KeyboardInterrupt:
1195 print 'shutting down server'
1196 server.stop = True
1197
1198if __name__ == '__main__':
1199 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001200 option_parser.add_option("-f", '--ftp', action='store_const',
1201 const=SERVER_FTP, default=SERVER_HTTP,
1202 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001203 help='FTP or HTTP server: default is HTTP.')
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001204 option_parser.add_option('--forking', action='store_true', default=False,
1205 dest='forking',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001206 help='Serve each request in a separate process.')
initial.commit94958cf2008-07-26 22:42:52 +00001207 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001208 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001209 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001210 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001211 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001212 help='Specify that https should be used, specify '
1213 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001214 'the server should use.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001215 option_parser.add_option('', '--file-root-url', default='/files/',
1216 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001217 option_parser.add_option('', '--never-die', default=False,
1218 action="store_true",
1219 help='Prevent the server from dying when visiting '
1220 'a /kill URL. Useful for manually running some '
1221 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001222 options, args = option_parser.parse_args()
1223
1224 sys.exit(main(options, args))