blob: e950ff8a0df30dfb3b800e63cd5da388781c673f [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00002# Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
6"""This is a simple HTTP server used for testing Chrome.
7
8It supports several test URLs, as specified by the handlers in TestPageHandler.
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
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000025import urllib2
26
27import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000028import tlslite
29import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000030
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000031try:
32 import hashlib
33 _new_md5 = hashlib.md5
34except ImportError:
35 import md5
36 _new_md5 = md5.new
37
maruel@chromium.org756cf982009-03-05 12:46:38 +000038SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000039SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000040
41debug_output = sys.stderr
42def debug(str):
43 debug_output.write(str + "\n")
44 debug_output.flush()
45
46class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
47 """This is a specialization of of BaseHTTPServer to allow it
48 to be exited cleanly (by setting its "stop" member to True)."""
49
50 def serve_forever(self):
51 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000052 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000053 while not self.stop:
54 self.handle_request()
55 self.socket.close()
56
57class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
58 """This is a specialization of StoppableHTTPerver that add https support."""
59
60 def __init__(self, server_address, request_hander_class, cert_path):
61 s = open(cert_path).read()
62 x509 = tlslite.api.X509()
63 x509.parse(s)
64 self.cert_chain = tlslite.api.X509CertChain([x509])
65 s = open(cert_path).read()
66 self.private_key = tlslite.api.parsePEMKey(s, private=True)
67
68 self.session_cache = tlslite.api.SessionCache()
69 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
70
71 def handshake(self, tlsConnection):
72 """Creates the SSL connection."""
73 try:
74 tlsConnection.handshakeServer(certChain=self.cert_chain,
75 privateKey=self.private_key,
76 sessionCache=self.session_cache)
77 tlsConnection.ignoreAbruptClose = True
78 return True
79 except tlslite.api.TLSError, error:
80 print "Handshake failure:", str(error)
81 return False
82
83class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
84
85 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000086 self._connect_handlers = [
87 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000088 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000089 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +000090 self._get_handlers = [
91 self.KillHandler,
92 self.NoCacheMaxAgeTimeHandler,
93 self.NoCacheTimeHandler,
94 self.CacheTimeHandler,
95 self.CacheExpiresHandler,
96 self.CacheProxyRevalidateHandler,
97 self.CachePrivateHandler,
98 self.CachePublicHandler,
99 self.CacheSMaxAgeHandler,
100 self.CacheMustRevalidateHandler,
101 self.CacheMustRevalidateMaxAgeHandler,
102 self.CacheNoStoreHandler,
103 self.CacheNoStoreMaxAgeHandler,
104 self.CacheNoTransformHandler,
105 self.DownloadHandler,
106 self.DownloadFinishHandler,
107 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000108 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000109 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000110 self.FileHandler,
111 self.RealFileWithCommonHeaderHandler,
112 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000113 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000114 self.AuthBasicHandler,
115 self.AuthDigestHandler,
116 self.SlowServerHandler,
117 self.ContentTypeHandler,
118 self.ServerRedirectHandler,
119 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000120 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000121 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000122 self.DefaultResponseHandler]
123 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000124 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000125 self.EchoTitleHandler,
126 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000127 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000128 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000129 self._put_handlers = [
130 self.WriteFile,
131 self.EchoTitleHandler,
132 self.EchoAllHandler,
133 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000134
maruel@google.come250a9b2009-03-10 17:39:46 +0000135 self._mime_types = {
136 'gif': 'image/gif',
137 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000138 'jpg' : 'image/jpeg',
139 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000140 }
initial.commit94958cf2008-07-26 22:42:52 +0000141 self._default_mime_type = 'text/html'
142
maruel@google.come250a9b2009-03-10 17:39:46 +0000143 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
144 client_address,
145 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000146
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000147 def _ShouldHandleRequest(self, handler_name):
148 """Determines if the path can be handled by the handler.
149
150 We consider a handler valid if the path begins with the
151 handler name. It can optionally be followed by "?*", "/*".
152 """
153
154 pattern = re.compile('%s($|\?|/).*' % handler_name)
155 return pattern.match(self.path)
156
initial.commit94958cf2008-07-26 22:42:52 +0000157 def GetMIMETypeFromName(self, file_name):
158 """Returns the mime type for the specified file_name. So far it only looks
159 at the file extension."""
160
161 (shortname, extension) = os.path.splitext(file_name)
162 if len(extension) == 0:
163 # no extension.
164 return self._default_mime_type
165
ericroman@google.comc17ca532009-05-07 03:51:05 +0000166 # extension starts with a dot, so we need to remove it
167 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000168
169 def KillHandler(self):
170 """This request handler kills the server, for use when we're done"
171 with the a particular test."""
172
173 if (self.path.find("kill") < 0):
174 return False
175
176 self.send_response(200)
177 self.send_header('Content-type', 'text/html')
178 self.send_header('Cache-Control', 'max-age=0')
179 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000180 if options.never_die:
181 self.wfile.write('I cannot die!! BWAHAHA')
182 else:
183 self.wfile.write('Goodbye cruel world!')
184 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000185
186 return True
187
188 def NoCacheMaxAgeTimeHandler(self):
189 """This request handler yields a page with the title set to the current
190 system time, and no caching requested."""
191
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000192 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000193 return False
194
195 self.send_response(200)
196 self.send_header('Cache-Control', 'max-age=0')
197 self.send_header('Content-type', 'text/html')
198 self.end_headers()
199
maruel@google.come250a9b2009-03-10 17:39:46 +0000200 self.wfile.write('<html><head><title>%s</title></head></html>' %
201 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000202
203 return True
204
205 def NoCacheTimeHandler(self):
206 """This request handler yields a page with the title set to the current
207 system time, and no caching requested."""
208
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000209 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000210 return False
211
212 self.send_response(200)
213 self.send_header('Cache-Control', 'no-cache')
214 self.send_header('Content-type', 'text/html')
215 self.end_headers()
216
maruel@google.come250a9b2009-03-10 17:39:46 +0000217 self.wfile.write('<html><head><title>%s</title></head></html>' %
218 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000219
220 return True
221
222 def CacheTimeHandler(self):
223 """This request handler yields a page with the title set to the current
224 system time, and allows caching for one minute."""
225
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000226 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000227 return False
228
229 self.send_response(200)
230 self.send_header('Cache-Control', 'max-age=60')
231 self.send_header('Content-type', 'text/html')
232 self.end_headers()
233
maruel@google.come250a9b2009-03-10 17:39:46 +0000234 self.wfile.write('<html><head><title>%s</title></head></html>' %
235 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000236
237 return True
238
239 def CacheExpiresHandler(self):
240 """This request handler yields a page with the title set to the current
241 system time, and set the page to expire on 1 Jan 2099."""
242
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000243 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000244 return False
245
246 self.send_response(200)
247 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
248 self.send_header('Content-type', 'text/html')
249 self.end_headers()
250
maruel@google.come250a9b2009-03-10 17:39:46 +0000251 self.wfile.write('<html><head><title>%s</title></head></html>' %
252 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000253
254 return True
255
256 def CacheProxyRevalidateHandler(self):
257 """This request handler yields a page with the title set to the current
258 system time, and allows caching for 60 seconds"""
259
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000260 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000261 return False
262
263 self.send_response(200)
264 self.send_header('Content-type', 'text/html')
265 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
266 self.end_headers()
267
maruel@google.come250a9b2009-03-10 17:39:46 +0000268 self.wfile.write('<html><head><title>%s</title></head></html>' %
269 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000270
271 return True
272
273 def CachePrivateHandler(self):
274 """This request handler yields a page with the title set to the current
275 system time, and allows caching for 5 seconds."""
276
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000277 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000278 return False
279
280 self.send_response(200)
281 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000282 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000283 self.end_headers()
284
maruel@google.come250a9b2009-03-10 17:39:46 +0000285 self.wfile.write('<html><head><title>%s</title></head></html>' %
286 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000287
288 return True
289
290 def CachePublicHandler(self):
291 """This request handler yields a page with the title set to the current
292 system time, and allows caching for 5 seconds."""
293
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000294 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000295 return False
296
297 self.send_response(200)
298 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000299 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000300 self.end_headers()
301
maruel@google.come250a9b2009-03-10 17:39:46 +0000302 self.wfile.write('<html><head><title>%s</title></head></html>' %
303 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000304
305 return True
306
307 def CacheSMaxAgeHandler(self):
308 """This request handler yields a page with the title set to the current
309 system time, and does not allow for caching."""
310
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000311 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000312 return False
313
314 self.send_response(200)
315 self.send_header('Content-type', 'text/html')
316 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
317 self.end_headers()
318
maruel@google.come250a9b2009-03-10 17:39:46 +0000319 self.wfile.write('<html><head><title>%s</title></head></html>' %
320 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000321
322 return True
323
324 def CacheMustRevalidateHandler(self):
325 """This request handler yields a page with the title set to the current
326 system time, and does not allow caching."""
327
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000328 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000329 return False
330
331 self.send_response(200)
332 self.send_header('Content-type', 'text/html')
333 self.send_header('Cache-Control', 'must-revalidate')
334 self.end_headers()
335
maruel@google.come250a9b2009-03-10 17:39:46 +0000336 self.wfile.write('<html><head><title>%s</title></head></html>' %
337 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000338
339 return True
340
341 def CacheMustRevalidateMaxAgeHandler(self):
342 """This request handler yields a page with the title set to the current
343 system time, and does not allow caching event though max-age of 60
344 seconds is specified."""
345
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000346 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000347 return False
348
349 self.send_response(200)
350 self.send_header('Content-type', 'text/html')
351 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
352 self.end_headers()
353
maruel@google.come250a9b2009-03-10 17:39:46 +0000354 self.wfile.write('<html><head><title>%s</title></head></html>' %
355 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000356
357 return True
358
initial.commit94958cf2008-07-26 22:42:52 +0000359 def CacheNoStoreHandler(self):
360 """This request handler yields a page with the title set to the current
361 system time, and does not allow the page to be stored."""
362
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000363 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000364 return False
365
366 self.send_response(200)
367 self.send_header('Content-type', 'text/html')
368 self.send_header('Cache-Control', 'no-store')
369 self.end_headers()
370
maruel@google.come250a9b2009-03-10 17:39:46 +0000371 self.wfile.write('<html><head><title>%s</title></head></html>' %
372 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000373
374 return True
375
376 def CacheNoStoreMaxAgeHandler(self):
377 """This request handler yields a page with the title set to the current
378 system time, and does not allow the page to be stored even though max-age
379 of 60 seconds is specified."""
380
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000381 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000382 return False
383
384 self.send_response(200)
385 self.send_header('Content-type', 'text/html')
386 self.send_header('Cache-Control', 'max-age=60, no-store')
387 self.end_headers()
388
maruel@google.come250a9b2009-03-10 17:39:46 +0000389 self.wfile.write('<html><head><title>%s</title></head></html>' %
390 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000391
392 return True
393
394
395 def CacheNoTransformHandler(self):
396 """This request handler yields a page with the title set to the current
397 system time, and does not allow the content to transformed during
398 user-agent caching"""
399
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000400 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000401 return False
402
403 self.send_response(200)
404 self.send_header('Content-type', 'text/html')
405 self.send_header('Cache-Control', 'no-transform')
406 self.end_headers()
407
maruel@google.come250a9b2009-03-10 17:39:46 +0000408 self.wfile.write('<html><head><title>%s</title></head></html>' %
409 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000410
411 return True
412
413 def EchoHeader(self):
414 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000415 """The only difference between this function and the EchoHeaderOverride"""
416 """function is in the parameter being passed to the helper function"""
417 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000418
ananta@chromium.org219b2062009-10-23 16:09:41 +0000419 def EchoHeaderOverride(self):
420 """This handler echoes back the value of a specific request header."""
421 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
422 """IE to issue HTTP requests using the host network stack."""
423 """The Accept and Charset tests which expect the server to echo back"""
424 """the corresponding headers fail here as IE returns cached responses"""
425 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
426 """treats this request as a new request and does not cache it."""
427 return self.EchoHeaderHelper("/echoheaderoverride")
428
429 def EchoHeaderHelper(self, echo_header):
430 """This function echoes back the value of the request header passed in."""
431 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000432 return False
433
434 query_char = self.path.find('?')
435 if query_char != -1:
436 header_name = self.path[query_char+1:]
437
438 self.send_response(200)
439 self.send_header('Content-type', 'text/plain')
440 self.send_header('Cache-control', 'max-age=60000')
441 # insert a vary header to properly indicate that the cachability of this
442 # request is subject to value of the request header being echoed.
443 if len(header_name) > 0:
444 self.send_header('Vary', header_name)
445 self.end_headers()
446
447 if len(header_name) > 0:
448 self.wfile.write(self.headers.getheader(header_name))
449
450 return True
451
452 def EchoHandler(self):
453 """This handler just echoes back the payload of the request, for testing
454 form submission."""
455
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000456 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000457 return False
458
459 self.send_response(200)
460 self.send_header('Content-type', 'text/html')
461 self.end_headers()
462 length = int(self.headers.getheader('content-length'))
463 request = self.rfile.read(length)
464 self.wfile.write(request)
465 return True
466
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000467 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000468 """This is handler dumps the content of POST/PUT request to a disk file
469 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000470
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000471 prefix='/writefile/'
472 if not self.path.startswith(prefix):
473 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000474
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000475 file_name = self.path[len(prefix):]
476
477 # do not allow fancy chars in file name
478 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
479 if len(file_name) and file_name[0] != '.':
480 path = os.path.join(self.server.data_dir, 'dump', file_name);
481 length = int(self.headers.getheader('content-length'))
482 request = self.rfile.read(length)
483 f = open(path, "wb")
484 f.write(request);
485 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000486
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000487 self.send_response(200)
488 self.send_header('Content-type', 'text/html')
489 self.end_headers()
490 self.wfile.write('<html>%s</html>' % file_name)
491 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000492
initial.commit94958cf2008-07-26 22:42:52 +0000493 def EchoTitleHandler(self):
494 """This handler is like Echo, but sets the page title to the request."""
495
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000496 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000497 return False
498
499 self.send_response(200)
500 self.send_header('Content-type', 'text/html')
501 self.end_headers()
502 length = int(self.headers.getheader('content-length'))
503 request = self.rfile.read(length)
504 self.wfile.write('<html><head><title>')
505 self.wfile.write(request)
506 self.wfile.write('</title></head></html>')
507 return True
508
509 def EchoAllHandler(self):
510 """This handler yields a (more) human-readable page listing information
511 about the request header & contents."""
512
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000513 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000514 return False
515
516 self.send_response(200)
517 self.send_header('Content-type', 'text/html')
518 self.end_headers()
519 self.wfile.write('<html><head><style>'
520 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
521 '</style></head><body>'
522 '<div style="float: right">'
523 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
524 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000525
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000526 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000527 length = int(self.headers.getheader('content-length'))
528 qs = self.rfile.read(length)
529 params = cgi.parse_qs(qs, keep_blank_values=1)
530
531 for param in params:
532 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000533
534 self.wfile.write('</pre>')
535
536 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
537
538 self.wfile.write('</body></html>')
539 return True
540
541 def DownloadHandler(self):
542 """This handler sends a downloadable file with or without reporting
543 the size (6K)."""
544
545 if self.path.startswith("/download-unknown-size"):
546 send_length = False
547 elif self.path.startswith("/download-known-size"):
548 send_length = True
549 else:
550 return False
551
552 #
553 # The test which uses this functionality is attempting to send
554 # small chunks of data to the client. Use a fairly large buffer
555 # so that we'll fill chrome's IO buffer enough to force it to
556 # actually write the data.
557 # See also the comments in the client-side of this test in
558 # download_uitest.cc
559 #
560 size_chunk1 = 35*1024
561 size_chunk2 = 10*1024
562
563 self.send_response(200)
564 self.send_header('Content-type', 'application/octet-stream')
565 self.send_header('Cache-Control', 'max-age=0')
566 if send_length:
567 self.send_header('Content-Length', size_chunk1 + size_chunk2)
568 self.end_headers()
569
570 # First chunk of data:
571 self.wfile.write("*" * size_chunk1)
572 self.wfile.flush()
573
574 # handle requests until one of them clears this flag.
575 self.server.waitForDownload = True
576 while self.server.waitForDownload:
577 self.server.handle_request()
578
579 # Second chunk of data:
580 self.wfile.write("*" * size_chunk2)
581 return True
582
583 def DownloadFinishHandler(self):
584 """This handler just tells the server to finish the current download."""
585
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000586 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000587 return False
588
589 self.server.waitForDownload = False
590 self.send_response(200)
591 self.send_header('Content-type', 'text/html')
592 self.send_header('Cache-Control', 'max-age=0')
593 self.end_headers()
594 return True
595
596 def FileHandler(self):
597 """This handler sends the contents of the requested file. Wow, it's like
598 a real webserver!"""
599
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000600 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000601 if not self.path.startswith(prefix):
602 return False
603
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000604 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000605 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000606 self.rfile.read(int(self.headers.getheader('content-length')))
607
initial.commit94958cf2008-07-26 22:42:52 +0000608 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000609 if file.find('?') > -1:
610 # Ignore the query parameters entirely.
611 url, querystring = file.split('?')
612 else:
613 url = file
614 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000615 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000616 if os.path.isdir(path):
617 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000618
619 if not os.path.isfile(path):
620 print "File not found " + file + " full path:" + path
621 self.send_error(404)
622 return True
623
624 f = open(path, "rb")
625 data = f.read()
626 f.close()
627
628 # If file.mock-http-headers exists, it contains the headers we
629 # should send. Read them in and parse them.
630 headers_path = path + '.mock-http-headers'
631 if os.path.isfile(headers_path):
632 f = open(headers_path, "r")
633
634 # "HTTP/1.1 200 OK"
635 response = f.readline()
636 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
637 self.send_response(int(status_code))
638
639 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000640 header_values = re.findall('(\S+):\s*(.*)', line)
641 if len(header_values) > 0:
642 # "name: value"
643 name, value = header_values[0]
644 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000645 f.close()
646 else:
647 # Could be more generic once we support mime-type sniffing, but for
648 # now we need to set it explicitly.
649 self.send_response(200)
650 self.send_header('Content-type', self.GetMIMETypeFromName(file))
651 self.send_header('Content-Length', len(data))
652 self.end_headers()
653
654 self.wfile.write(data)
655
656 return True
657
658 def RealFileWithCommonHeaderHandler(self):
659 """This handler sends the contents of the requested file without the pseudo
660 http head!"""
661
662 prefix='/realfiles/'
663 if not self.path.startswith(prefix):
664 return False
665
666 file = self.path[len(prefix):]
667 path = os.path.join(self.server.data_dir, file)
668
669 try:
670 f = open(path, "rb")
671 data = f.read()
672 f.close()
673
674 # just simply set the MIME as octal stream
675 self.send_response(200)
676 self.send_header('Content-type', 'application/octet-stream')
677 self.end_headers()
678
679 self.wfile.write(data)
680 except:
681 self.send_error(404)
682
683 return True
684
685 def RealBZ2FileWithCommonHeaderHandler(self):
686 """This handler sends the bzip2 contents of the requested file with
687 corresponding Content-Encoding field in http head!"""
688
689 prefix='/realbz2files/'
690 if not self.path.startswith(prefix):
691 return False
692
693 parts = self.path.split('?')
694 file = parts[0][len(prefix):]
695 path = os.path.join(self.server.data_dir, file) + '.bz2'
696
697 if len(parts) > 1:
698 options = parts[1]
699 else:
700 options = ''
701
702 try:
703 self.send_response(200)
704 accept_encoding = self.headers.get("Accept-Encoding")
705 if accept_encoding.find("bzip2") != -1:
706 f = open(path, "rb")
707 data = f.read()
708 f.close()
709 self.send_header('Content-Encoding', 'bzip2')
710 self.send_header('Content-type', 'application/x-bzip2')
711 self.end_headers()
712 if options == 'incremental-header':
713 self.wfile.write(data[:1])
714 self.wfile.flush()
715 time.sleep(1.0)
716 self.wfile.write(data[1:])
717 else:
718 self.wfile.write(data)
719 else:
720 """client do not support bzip2 format, send pseudo content
721 """
722 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
723 self.end_headers()
724 self.wfile.write("you do not support bzip2 encoding")
725 except:
726 self.send_error(404)
727
728 return True
729
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000730 def SetCookieHandler(self):
731 """This handler just sets a cookie, for testing cookie handling."""
732
733 if not self._ShouldHandleRequest("/set-cookie"):
734 return False
735
736 query_char = self.path.find('?')
737 if query_char != -1:
738 cookie_values = self.path[query_char + 1:].split('&')
739 else:
740 cookie_values = ("",)
741 self.send_response(200)
742 self.send_header('Content-type', 'text/html')
743 for cookie_value in cookie_values:
744 self.send_header('Set-Cookie', '%s' % cookie_value)
745 self.end_headers()
746 for cookie_value in cookie_values:
747 self.wfile.write('%s' % cookie_value)
748 return True
749
initial.commit94958cf2008-07-26 22:42:52 +0000750 def AuthBasicHandler(self):
751 """This handler tests 'Basic' authentication. It just sends a page with
752 title 'user/pass' if you succeed."""
753
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000754 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000755 return False
756
757 username = userpass = password = b64str = ""
758
ericroman@google.com239b4d82009-03-27 04:00:22 +0000759 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
760
initial.commit94958cf2008-07-26 22:42:52 +0000761 auth = self.headers.getheader('authorization')
762 try:
763 if not auth:
764 raise Exception('no auth')
765 b64str = re.findall(r'Basic (\S+)', auth)[0]
766 userpass = base64.b64decode(b64str)
767 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
768 if password != 'secret':
769 raise Exception('wrong password')
770 except Exception, e:
771 # Authentication failed.
772 self.send_response(401)
773 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
774 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000775 if set_cookie_if_challenged:
776 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000777 self.end_headers()
778 self.wfile.write('<html><head>')
779 self.wfile.write('<title>Denied: %s</title>' % e)
780 self.wfile.write('</head><body>')
781 self.wfile.write('auth=%s<p>' % auth)
782 self.wfile.write('b64str=%s<p>' % b64str)
783 self.wfile.write('username: %s<p>' % username)
784 self.wfile.write('userpass: %s<p>' % userpass)
785 self.wfile.write('password: %s<p>' % password)
786 self.wfile.write('You sent:<br>%s<p>' % self.headers)
787 self.wfile.write('</body></html>')
788 return True
789
790 # Authentication successful. (Return a cachable response to allow for
791 # testing cached pages that require authentication.)
792 if_none_match = self.headers.getheader('if-none-match')
793 if if_none_match == "abc":
794 self.send_response(304)
795 self.end_headers()
796 else:
797 self.send_response(200)
798 self.send_header('Content-type', 'text/html')
799 self.send_header('Cache-control', 'max-age=60000')
800 self.send_header('Etag', 'abc')
801 self.end_headers()
802 self.wfile.write('<html><head>')
803 self.wfile.write('<title>%s/%s</title>' % (username, password))
804 self.wfile.write('</head><body>')
805 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000806 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000807 self.wfile.write('</body></html>')
808
809 return True
810
tonyg@chromium.org75054202010-03-31 22:06:10 +0000811 def GetNonce(self, force_reset=False):
812 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000813
tonyg@chromium.org75054202010-03-31 22:06:10 +0000814 This is a fake implementation. A real implementation would only use a given
815 nonce a single time (hence the name n-once). However, for the purposes of
816 unittesting, we don't care about the security of the nonce.
817
818 Args:
819 force_reset: Iff set, the nonce will be changed. Useful for testing the
820 "stale" response.
821 """
822 if force_reset or not self.server.nonce_time:
823 self.server.nonce_time = time.time()
824 return _new_md5('privatekey%s%d' %
825 (self.path, self.server.nonce_time)).hexdigest()
826
827 def AuthDigestHandler(self):
828 """This handler tests 'Digest' authentication.
829
830 It just sends a page with title 'user/pass' if you succeed.
831
832 A stale response is sent iff "stale" is present in the request path.
833 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000834 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000835 return False
836
tonyg@chromium.org75054202010-03-31 22:06:10 +0000837 stale = 'stale' in self.path
838 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000839 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000840 password = 'secret'
841 realm = 'testrealm'
842
843 auth = self.headers.getheader('authorization')
844 pairs = {}
845 try:
846 if not auth:
847 raise Exception('no auth')
848 if not auth.startswith('Digest'):
849 raise Exception('not digest')
850 # Pull out all the name="value" pairs as a dictionary.
851 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
852
853 # Make sure it's all valid.
854 if pairs['nonce'] != nonce:
855 raise Exception('wrong nonce')
856 if pairs['opaque'] != opaque:
857 raise Exception('wrong opaque')
858
859 # Check the 'response' value and make sure it matches our magic hash.
860 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000861 hash_a1 = _new_md5(
862 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000863 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000864 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000865 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000866 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
867 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000868 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000869
870 if pairs['response'] != response:
871 raise Exception('wrong password')
872 except Exception, e:
873 # Authentication failed.
874 self.send_response(401)
875 hdr = ('Digest '
876 'realm="%s", '
877 'domain="/", '
878 'qop="auth", '
879 'algorithm=MD5, '
880 'nonce="%s", '
881 'opaque="%s"') % (realm, nonce, opaque)
882 if stale:
883 hdr += ', stale="TRUE"'
884 self.send_header('WWW-Authenticate', hdr)
885 self.send_header('Content-type', 'text/html')
886 self.end_headers()
887 self.wfile.write('<html><head>')
888 self.wfile.write('<title>Denied: %s</title>' % e)
889 self.wfile.write('</head><body>')
890 self.wfile.write('auth=%s<p>' % auth)
891 self.wfile.write('pairs=%s<p>' % pairs)
892 self.wfile.write('You sent:<br>%s<p>' % self.headers)
893 self.wfile.write('We are replying:<br>%s<p>' % hdr)
894 self.wfile.write('</body></html>')
895 return True
896
897 # Authentication successful.
898 self.send_response(200)
899 self.send_header('Content-type', 'text/html')
900 self.end_headers()
901 self.wfile.write('<html><head>')
902 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
903 self.wfile.write('</head><body>')
904 self.wfile.write('auth=%s<p>' % auth)
905 self.wfile.write('pairs=%s<p>' % pairs)
906 self.wfile.write('</body></html>')
907
908 return True
909
910 def SlowServerHandler(self):
911 """Wait for the user suggested time before responding. The syntax is
912 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000913 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000914 return False
915 query_char = self.path.find('?')
916 wait_sec = 1.0
917 if query_char >= 0:
918 try:
919 wait_sec = int(self.path[query_char + 1:])
920 except ValueError:
921 pass
922 time.sleep(wait_sec)
923 self.send_response(200)
924 self.send_header('Content-type', 'text/plain')
925 self.end_headers()
926 self.wfile.write("waited %d seconds" % wait_sec)
927 return True
928
929 def ContentTypeHandler(self):
930 """Returns a string of html with the given content type. E.g.,
931 /contenttype?text/css returns an html file with the Content-Type
932 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000933 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000934 return False
935 query_char = self.path.find('?')
936 content_type = self.path[query_char + 1:].strip()
937 if not content_type:
938 content_type = 'text/html'
939 self.send_response(200)
940 self.send_header('Content-Type', content_type)
941 self.end_headers()
942 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
943 return True
944
945 def ServerRedirectHandler(self):
946 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000947 '/server-redirect?http://foo.bar/asdf' to redirect to
948 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000949
950 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000951 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000952 return False
953
954 query_char = self.path.find('?')
955 if query_char < 0 or len(self.path) <= query_char + 1:
956 self.sendRedirectHelp(test_name)
957 return True
958 dest = self.path[query_char + 1:]
959
960 self.send_response(301) # moved permanently
961 self.send_header('Location', dest)
962 self.send_header('Content-type', 'text/html')
963 self.end_headers()
964 self.wfile.write('<html><head>')
965 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
966
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000967 return True
initial.commit94958cf2008-07-26 22:42:52 +0000968
969 def ClientRedirectHandler(self):
970 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000971 '/client-redirect?http://foo.bar/asdf' to redirect to
972 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000973
974 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000975 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000976 return False
977
978 query_char = self.path.find('?');
979 if query_char < 0 or len(self.path) <= query_char + 1:
980 self.sendRedirectHelp(test_name)
981 return True
982 dest = self.path[query_char + 1:]
983
984 self.send_response(200)
985 self.send_header('Content-type', 'text/html')
986 self.end_headers()
987 self.wfile.write('<html><head>')
988 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
989 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
990
991 return True
992
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000993 def ChromiumSyncTimeHandler(self):
994 """Handle Chromium sync .../time requests.
995
996 The syncer sometimes checks server reachability by examining /time.
997 """
998 test_name = "/chromiumsync/time"
999 if not self._ShouldHandleRequest(test_name):
1000 return False
1001
1002 self.send_response(200)
1003 self.send_header('Content-type', 'text/html')
1004 self.end_headers()
1005 return True
1006
1007 def ChromiumSyncCommandHandler(self):
1008 """Handle a chromiumsync command arriving via http.
1009
1010 This covers all sync protocol commands: authentication, getupdates, and
1011 commit.
1012 """
1013 test_name = "/chromiumsync/command"
1014 if not self._ShouldHandleRequest(test_name):
1015 return False
1016
1017 length = int(self.headers.getheader('content-length'))
1018 raw_request = self.rfile.read(length)
1019
pathorn@chromium.org44920122010-07-27 18:25:35 +00001020 if not self.server._sync_handler:
1021 import chromiumsync
1022 self.server._sync_handler = chromiumsync.TestServer()
1023 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1024 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001025 self.send_response(http_response)
1026 self.end_headers()
1027 self.wfile.write(raw_reply)
1028 return True
1029
tony@chromium.org03266982010-03-05 03:18:42 +00001030 def MultipartHandler(self):
1031 """Send a multipart response (10 text/html pages)."""
1032 test_name = "/multipart"
1033 if not self._ShouldHandleRequest(test_name):
1034 return False
1035
1036 num_frames = 10
1037 bound = '12345'
1038 self.send_response(200)
1039 self.send_header('Content-type',
1040 'multipart/x-mixed-replace;boundary=' + bound)
1041 self.end_headers()
1042
1043 for i in xrange(num_frames):
1044 self.wfile.write('--' + bound + '\r\n')
1045 self.wfile.write('Content-type: text/html\r\n\r\n')
1046 self.wfile.write('<title>page ' + str(i) + '</title>')
1047 self.wfile.write('page ' + str(i))
1048
1049 self.wfile.write('--' + bound + '--')
1050 return True
1051
initial.commit94958cf2008-07-26 22:42:52 +00001052 def DefaultResponseHandler(self):
1053 """This is the catch-all response handler for requests that aren't handled
1054 by one of the special handlers above.
1055 Note that we specify the content-length as without it the https connection
1056 is not closed properly (and the browser keeps expecting data)."""
1057
1058 contents = "Default response given for path: " + self.path
1059 self.send_response(200)
1060 self.send_header('Content-type', 'text/html')
1061 self.send_header("Content-Length", len(contents))
1062 self.end_headers()
1063 self.wfile.write(contents)
1064 return True
1065
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001066 def RedirectConnectHandler(self):
1067 """Sends a redirect to the CONNECT request for www.redirect.com. This
1068 response is not specified by the RFC, so the browser should not follow
1069 the redirect."""
1070
1071 if (self.path.find("www.redirect.com") < 0):
1072 return False
1073
1074 dest = "http://www.destination.com/foo.js"
1075
1076 self.send_response(302) # moved temporarily
1077 self.send_header('Location', dest)
1078 self.send_header('Connection', 'close')
1079 self.end_headers()
1080 return True
1081
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001082 def ServerAuthConnectHandler(self):
1083 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1084 response doesn't make sense because the proxy server cannot request
1085 server authentication."""
1086
1087 if (self.path.find("www.server-auth.com") < 0):
1088 return False
1089
1090 challenge = 'Basic realm="WallyWorld"'
1091
1092 self.send_response(401) # unauthorized
1093 self.send_header('WWW-Authenticate', challenge)
1094 self.send_header('Connection', 'close')
1095 self.end_headers()
1096 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001097
1098 def DefaultConnectResponseHandler(self):
1099 """This is the catch-all response handler for CONNECT requests that aren't
1100 handled by one of the special handlers above. Real Web servers respond
1101 with 400 to CONNECT requests."""
1102
1103 contents = "Your client has issued a malformed or illegal request."
1104 self.send_response(400) # bad request
1105 self.send_header('Content-type', 'text/html')
1106 self.send_header("Content-Length", len(contents))
1107 self.end_headers()
1108 self.wfile.write(contents)
1109 return True
1110
1111 def do_CONNECT(self):
1112 for handler in self._connect_handlers:
1113 if handler():
1114 return
1115
initial.commit94958cf2008-07-26 22:42:52 +00001116 def do_GET(self):
1117 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001118 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001119 return
1120
1121 def do_POST(self):
1122 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001123 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001124 return
1125
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001126 def do_PUT(self):
1127 for handler in self._put_handlers:
1128 if handler():
1129 return
1130
initial.commit94958cf2008-07-26 22:42:52 +00001131 # called by the redirect handling function when there is no parameter
1132 def sendRedirectHelp(self, redirect_name):
1133 self.send_response(200)
1134 self.send_header('Content-type', 'text/html')
1135 self.end_headers()
1136 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1137 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1138 self.wfile.write('</body></html>')
1139
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001140def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001141 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1142 requests will be stored. If the directory already exists all files and
1143 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001144 dump_dir = os.path.join(data_dir, 'dump');
1145 if os.path.isdir(dump_dir):
1146 shutil.rmtree(dump_dir)
1147 os.mkdir(dump_dir)
1148
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001149def MakeDataDir():
1150 if options.data_dir:
1151 if not os.path.isdir(options.data_dir):
1152 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1153 return None
1154 my_data_dir = options.data_dir
1155 else:
1156 # Create the default path to our data dir, relative to the exe dir.
1157 my_data_dir = os.path.dirname(sys.argv[0])
1158 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001159 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001160
1161 #TODO(ibrar): Must use Find* funtion defined in google\tools
1162 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1163
1164 return my_data_dir
1165
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001166def TryKillingOldServer(port):
1167 # Note that an HTTP /kill request to the FTP server has the effect of
1168 # killing it.
1169 for protocol in ["http", "https"]:
1170 try:
1171 urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
1172 print "Killed old server instance on port %d (via %s)" % (port, protocol)
1173 except urllib2.URLError:
1174 # Common case, indicates no server running.
1175 pass
1176
initial.commit94958cf2008-07-26 22:42:52 +00001177def main(options, args):
1178 # redirect output to a log file so it doesn't spam the unit test output
1179 logfile = open('testserver.log', 'w')
1180 sys.stderr = sys.stdout = logfile
1181
1182 port = options.port
1183
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001184 # Try to free up the port if there's an orphaned old instance.
1185 TryKillingOldServer(port)
1186
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001187 if options.server_type == SERVER_HTTP:
1188 if options.cert:
1189 # let's make sure the cert file exists.
1190 if not os.path.isfile(options.cert):
1191 print 'specified cert file not found: ' + options.cert + ' exiting...'
1192 return
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001193 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001194 print 'HTTPS server started on port %d...' % port
1195 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001196 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001197 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001198
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001199 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001200 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001201 server._sync_handler = None
1202
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001203 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001204
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001205 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001206 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001207 my_data_dir = MakeDataDir()
1208
1209 def line_logger(msg):
1210 if (msg.find("kill") >= 0):
1211 server.stop = True
1212 print 'shutting down server'
1213 sys.exit(0)
1214
1215 # Instantiate a dummy authorizer for managing 'virtual' users
1216 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1217
1218 # Define a new user having full r/w permissions and a read-only
1219 # anonymous user
1220 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1221
1222 authorizer.add_anonymous(my_data_dir)
1223
1224 # Instantiate FTP handler class
1225 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1226 ftp_handler.authorizer = authorizer
1227 pyftpdlib.ftpserver.logline = line_logger
1228
1229 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001230 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1231 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001232
1233 # Instantiate FTP server class and listen to 127.0.0.1:port
1234 address = ('127.0.0.1', port)
1235 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1236 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001237
1238 try:
1239 server.serve_forever()
1240 except KeyboardInterrupt:
1241 print 'shutting down server'
1242 server.stop = True
1243
1244if __name__ == '__main__':
1245 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001246 option_parser.add_option("-f", '--ftp', action='store_const',
1247 const=SERVER_FTP, default=SERVER_HTTP,
1248 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001249 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001250 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001251 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001252 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001253 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001254 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001255 help='Specify that https should be used, specify '
1256 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001257 'the server should use.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001258 option_parser.add_option('', '--file-root-url', default='/files/',
1259 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001260 option_parser.add_option('', '--never-die', default=False,
1261 action="store_true",
1262 help='Prevent the server from dying when visiting '
1263 'a /kill URL. Useful for manually running some '
1264 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001265 options, args = option_parser.parse_args()
1266
1267 sys.exit(main(options, args))