blob: 94ad3da7269230fef68be66402d161b07b8cb0f9 [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
tonyg@chromium.org75054202010-03-31 22:06:10 +000050 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000051 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
tonyg@chromium.org75054202010-03-31 22:06:10 +0000817 def GetNonce(self, force_reset=False):
818 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000819
tonyg@chromium.org75054202010-03-31 22:06:10 +0000820 This is a fake implementation. A real implementation would only use a given
821 nonce a single time (hence the name n-once). However, for the purposes of
822 unittesting, we don't care about the security of the nonce.
823
824 Args:
825 force_reset: Iff set, the nonce will be changed. Useful for testing the
826 "stale" response.
827 """
828 if force_reset or not self.server.nonce_time:
829 self.server.nonce_time = time.time()
830 return _new_md5('privatekey%s%d' %
831 (self.path, self.server.nonce_time)).hexdigest()
832
833 def AuthDigestHandler(self):
834 """This handler tests 'Digest' authentication.
835
836 It just sends a page with title 'user/pass' if you succeed.
837
838 A stale response is sent iff "stale" is present in the request path.
839 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000840 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000841 return False
842
tonyg@chromium.org75054202010-03-31 22:06:10 +0000843 stale = 'stale' in self.path
844 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000845 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000846 password = 'secret'
847 realm = 'testrealm'
848
849 auth = self.headers.getheader('authorization')
850 pairs = {}
851 try:
852 if not auth:
853 raise Exception('no auth')
854 if not auth.startswith('Digest'):
855 raise Exception('not digest')
856 # Pull out all the name="value" pairs as a dictionary.
857 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
858
859 # Make sure it's all valid.
860 if pairs['nonce'] != nonce:
861 raise Exception('wrong nonce')
862 if pairs['opaque'] != opaque:
863 raise Exception('wrong opaque')
864
865 # Check the 'response' value and make sure it matches our magic hash.
866 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000867 hash_a1 = _new_md5(
868 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000869 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000870 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000871 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000872 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
873 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000874 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000875
876 if pairs['response'] != response:
877 raise Exception('wrong password')
878 except Exception, e:
879 # Authentication failed.
880 self.send_response(401)
881 hdr = ('Digest '
882 'realm="%s", '
883 'domain="/", '
884 'qop="auth", '
885 'algorithm=MD5, '
886 'nonce="%s", '
887 'opaque="%s"') % (realm, nonce, opaque)
888 if stale:
889 hdr += ', stale="TRUE"'
890 self.send_header('WWW-Authenticate', hdr)
891 self.send_header('Content-type', 'text/html')
892 self.end_headers()
893 self.wfile.write('<html><head>')
894 self.wfile.write('<title>Denied: %s</title>' % e)
895 self.wfile.write('</head><body>')
896 self.wfile.write('auth=%s<p>' % auth)
897 self.wfile.write('pairs=%s<p>' % pairs)
898 self.wfile.write('You sent:<br>%s<p>' % self.headers)
899 self.wfile.write('We are replying:<br>%s<p>' % hdr)
900 self.wfile.write('</body></html>')
901 return True
902
903 # Authentication successful.
904 self.send_response(200)
905 self.send_header('Content-type', 'text/html')
906 self.end_headers()
907 self.wfile.write('<html><head>')
908 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
909 self.wfile.write('</head><body>')
910 self.wfile.write('auth=%s<p>' % auth)
911 self.wfile.write('pairs=%s<p>' % pairs)
912 self.wfile.write('</body></html>')
913
914 return True
915
916 def SlowServerHandler(self):
917 """Wait for the user suggested time before responding. The syntax is
918 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000919 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000920 return False
921 query_char = self.path.find('?')
922 wait_sec = 1.0
923 if query_char >= 0:
924 try:
925 wait_sec = int(self.path[query_char + 1:])
926 except ValueError:
927 pass
928 time.sleep(wait_sec)
929 self.send_response(200)
930 self.send_header('Content-type', 'text/plain')
931 self.end_headers()
932 self.wfile.write("waited %d seconds" % wait_sec)
933 return True
934
935 def ContentTypeHandler(self):
936 """Returns a string of html with the given content type. E.g.,
937 /contenttype?text/css returns an html file with the Content-Type
938 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000939 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000940 return False
941 query_char = self.path.find('?')
942 content_type = self.path[query_char + 1:].strip()
943 if not content_type:
944 content_type = 'text/html'
945 self.send_response(200)
946 self.send_header('Content-Type', content_type)
947 self.end_headers()
948 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
949 return True
950
951 def ServerRedirectHandler(self):
952 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000953 '/server-redirect?http://foo.bar/asdf' to redirect to
954 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000955
956 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000957 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000958 return False
959
960 query_char = self.path.find('?')
961 if query_char < 0 or len(self.path) <= query_char + 1:
962 self.sendRedirectHelp(test_name)
963 return True
964 dest = self.path[query_char + 1:]
965
966 self.send_response(301) # moved permanently
967 self.send_header('Location', dest)
968 self.send_header('Content-type', 'text/html')
969 self.end_headers()
970 self.wfile.write('<html><head>')
971 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
972
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000973 return True
initial.commit94958cf2008-07-26 22:42:52 +0000974
975 def ClientRedirectHandler(self):
976 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000977 '/client-redirect?http://foo.bar/asdf' to redirect to
978 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000979
980 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000981 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000982 return False
983
984 query_char = self.path.find('?');
985 if query_char < 0 or len(self.path) <= query_char + 1:
986 self.sendRedirectHelp(test_name)
987 return True
988 dest = self.path[query_char + 1:]
989
990 self.send_response(200)
991 self.send_header('Content-type', 'text/html')
992 self.end_headers()
993 self.wfile.write('<html><head>')
994 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
995 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
996
997 return True
998
tony@chromium.org03266982010-03-05 03:18:42 +0000999 def MultipartHandler(self):
1000 """Send a multipart response (10 text/html pages)."""
1001 test_name = "/multipart"
1002 if not self._ShouldHandleRequest(test_name):
1003 return False
1004
1005 num_frames = 10
1006 bound = '12345'
1007 self.send_response(200)
1008 self.send_header('Content-type',
1009 'multipart/x-mixed-replace;boundary=' + bound)
1010 self.end_headers()
1011
1012 for i in xrange(num_frames):
1013 self.wfile.write('--' + bound + '\r\n')
1014 self.wfile.write('Content-type: text/html\r\n\r\n')
1015 self.wfile.write('<title>page ' + str(i) + '</title>')
1016 self.wfile.write('page ' + str(i))
1017
1018 self.wfile.write('--' + bound + '--')
1019 return True
1020
initial.commit94958cf2008-07-26 22:42:52 +00001021 def DefaultResponseHandler(self):
1022 """This is the catch-all response handler for requests that aren't handled
1023 by one of the special handlers above.
1024 Note that we specify the content-length as without it the https connection
1025 is not closed properly (and the browser keeps expecting data)."""
1026
1027 contents = "Default response given for path: " + self.path
1028 self.send_response(200)
1029 self.send_header('Content-type', 'text/html')
1030 self.send_header("Content-Length", len(contents))
1031 self.end_headers()
1032 self.wfile.write(contents)
1033 return True
1034
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001035 def RedirectConnectHandler(self):
1036 """Sends a redirect to the CONNECT request for www.redirect.com. This
1037 response is not specified by the RFC, so the browser should not follow
1038 the redirect."""
1039
1040 if (self.path.find("www.redirect.com") < 0):
1041 return False
1042
1043 dest = "http://www.destination.com/foo.js"
1044
1045 self.send_response(302) # moved temporarily
1046 self.send_header('Location', dest)
1047 self.send_header('Connection', 'close')
1048 self.end_headers()
1049 return True
1050
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001051 def ServerAuthConnectHandler(self):
1052 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1053 response doesn't make sense because the proxy server cannot request
1054 server authentication."""
1055
1056 if (self.path.find("www.server-auth.com") < 0):
1057 return False
1058
1059 challenge = 'Basic realm="WallyWorld"'
1060
1061 self.send_response(401) # unauthorized
1062 self.send_header('WWW-Authenticate', challenge)
1063 self.send_header('Connection', 'close')
1064 self.end_headers()
1065 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001066
1067 def DefaultConnectResponseHandler(self):
1068 """This is the catch-all response handler for CONNECT requests that aren't
1069 handled by one of the special handlers above. Real Web servers respond
1070 with 400 to CONNECT requests."""
1071
1072 contents = "Your client has issued a malformed or illegal request."
1073 self.send_response(400) # bad request
1074 self.send_header('Content-type', 'text/html')
1075 self.send_header("Content-Length", len(contents))
1076 self.end_headers()
1077 self.wfile.write(contents)
1078 return True
1079
1080 def do_CONNECT(self):
1081 for handler in self._connect_handlers:
1082 if handler():
1083 return
1084
initial.commit94958cf2008-07-26 22:42:52 +00001085 def do_GET(self):
1086 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001087 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001088 return
1089
1090 def do_POST(self):
1091 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001092 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001093 return
1094
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001095 def do_PUT(self):
1096 for handler in self._put_handlers:
1097 if handler():
1098 return
1099
initial.commit94958cf2008-07-26 22:42:52 +00001100 # called by the redirect handling function when there is no parameter
1101 def sendRedirectHelp(self, redirect_name):
1102 self.send_response(200)
1103 self.send_header('Content-type', 'text/html')
1104 self.end_headers()
1105 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1106 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1107 self.wfile.write('</body></html>')
1108
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001109def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001110 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1111 requests will be stored. If the directory already exists all files and
1112 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001113 dump_dir = os.path.join(data_dir, 'dump');
1114 if os.path.isdir(dump_dir):
1115 shutil.rmtree(dump_dir)
1116 os.mkdir(dump_dir)
1117
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001118def MakeDataDir():
1119 if options.data_dir:
1120 if not os.path.isdir(options.data_dir):
1121 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1122 return None
1123 my_data_dir = options.data_dir
1124 else:
1125 # Create the default path to our data dir, relative to the exe dir.
1126 my_data_dir = os.path.dirname(sys.argv[0])
1127 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1128 "test", "data")
1129
1130 #TODO(ibrar): Must use Find* funtion defined in google\tools
1131 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1132
1133 return my_data_dir
1134
initial.commit94958cf2008-07-26 22:42:52 +00001135def main(options, args):
1136 # redirect output to a log file so it doesn't spam the unit test output
1137 logfile = open('testserver.log', 'w')
1138 sys.stderr = sys.stdout = logfile
1139
1140 port = options.port
1141
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001142 if options.server_type == SERVER_HTTP:
1143 if options.cert:
1144 # let's make sure the cert file exists.
1145 if not os.path.isfile(options.cert):
1146 print 'specified cert file not found: ' + options.cert + ' exiting...'
1147 return
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001148 if options.forking:
1149 server_class = ForkingHTTPSServer
1150 else:
1151 server_class = HTTPSServer
1152 server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001153 print 'HTTPS server started on port %d...' % port
1154 else:
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001155 if options.forking:
1156 server_class = ForkingHTTPServer
1157 else:
1158 server_class = StoppableHTTPServer
1159 server = server_class(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001160 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001161
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001162 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001163 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001164 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001165
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001166 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001167 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001168 my_data_dir = MakeDataDir()
1169
1170 def line_logger(msg):
1171 if (msg.find("kill") >= 0):
1172 server.stop = True
1173 print 'shutting down server'
1174 sys.exit(0)
1175
1176 # Instantiate a dummy authorizer for managing 'virtual' users
1177 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1178
1179 # Define a new user having full r/w permissions and a read-only
1180 # anonymous user
1181 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1182
1183 authorizer.add_anonymous(my_data_dir)
1184
1185 # Instantiate FTP handler class
1186 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1187 ftp_handler.authorizer = authorizer
1188 pyftpdlib.ftpserver.logline = line_logger
1189
1190 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001191 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1192 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001193
1194 # Instantiate FTP server class and listen to 127.0.0.1:port
1195 address = ('127.0.0.1', port)
1196 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1197 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001198
1199 try:
1200 server.serve_forever()
1201 except KeyboardInterrupt:
1202 print 'shutting down server'
1203 server.stop = True
1204
1205if __name__ == '__main__':
1206 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001207 option_parser.add_option("-f", '--ftp', action='store_const',
1208 const=SERVER_FTP, default=SERVER_HTTP,
1209 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001210 help='FTP or HTTP server: default is HTTP.')
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001211 option_parser.add_option('--forking', action='store_true', default=False,
1212 dest='forking',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001213 help='Serve each request in a separate process.')
initial.commit94958cf2008-07-26 22:42:52 +00001214 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001215 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001216 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001217 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001218 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001219 help='Specify that https should be used, specify '
1220 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001221 'the server should use.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001222 option_parser.add_option('', '--file-root-url', default='/files/',
1223 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001224 option_parser.add_option('', '--never-die', default=False,
1225 action="store_true",
1226 help='Prevent the server from dying when visiting '
1227 'a /kill URL. Useful for manually running some '
1228 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001229 options, args = option_parser.parse_args()
1230
1231 sys.exit(main(options, args))