blob: 0ad5d285f5357ac508080cec60709d24c0148c19 [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
davidben@chromium.org31282a12010-08-07 01:10:02 +000060 def __init__(self, server_address, request_hander_class, cert_path,
61 ssl_client_auth):
initial.commit94958cf2008-07-26 22:42:52 +000062 s = open(cert_path).read()
63 x509 = tlslite.api.X509()
64 x509.parse(s)
65 self.cert_chain = tlslite.api.X509CertChain([x509])
66 s = open(cert_path).read()
67 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000068 self.ssl_client_auth = ssl_client_auth
initial.commit94958cf2008-07-26 22:42:52 +000069
70 self.session_cache = tlslite.api.SessionCache()
71 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
72
73 def handshake(self, tlsConnection):
74 """Creates the SSL connection."""
75 try:
76 tlsConnection.handshakeServer(certChain=self.cert_chain,
77 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000078 sessionCache=self.session_cache,
79 reqCert=self.ssl_client_auth)
initial.commit94958cf2008-07-26 22:42:52 +000080 tlsConnection.ignoreAbruptClose = True
81 return True
82 except tlslite.api.TLSError, error:
83 print "Handshake failure:", str(error)
84 return False
85
86class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
87
88 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000089 self._connect_handlers = [
90 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000091 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000092 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +000093 self._get_handlers = [
94 self.KillHandler,
95 self.NoCacheMaxAgeTimeHandler,
96 self.NoCacheTimeHandler,
97 self.CacheTimeHandler,
98 self.CacheExpiresHandler,
99 self.CacheProxyRevalidateHandler,
100 self.CachePrivateHandler,
101 self.CachePublicHandler,
102 self.CacheSMaxAgeHandler,
103 self.CacheMustRevalidateHandler,
104 self.CacheMustRevalidateMaxAgeHandler,
105 self.CacheNoStoreHandler,
106 self.CacheNoStoreMaxAgeHandler,
107 self.CacheNoTransformHandler,
108 self.DownloadHandler,
109 self.DownloadFinishHandler,
110 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000111 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000112 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000113 self.FileHandler,
114 self.RealFileWithCommonHeaderHandler,
115 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000116 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000117 self.AuthBasicHandler,
118 self.AuthDigestHandler,
119 self.SlowServerHandler,
120 self.ContentTypeHandler,
121 self.ServerRedirectHandler,
122 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000123 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000124 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000125 self.DefaultResponseHandler]
126 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000127 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000128 self.EchoTitleHandler,
129 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000130 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000131 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000132 self._put_handlers = [
133 self.WriteFile,
134 self.EchoTitleHandler,
135 self.EchoAllHandler,
136 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000137
maruel@google.come250a9b2009-03-10 17:39:46 +0000138 self._mime_types = {
139 'gif': 'image/gif',
140 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000141 'jpg' : 'image/jpeg',
142 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000143 }
initial.commit94958cf2008-07-26 22:42:52 +0000144 self._default_mime_type = 'text/html'
145
maruel@google.come250a9b2009-03-10 17:39:46 +0000146 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
147 client_address,
148 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000149
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000150 def _ShouldHandleRequest(self, handler_name):
151 """Determines if the path can be handled by the handler.
152
153 We consider a handler valid if the path begins with the
154 handler name. It can optionally be followed by "?*", "/*".
155 """
156
157 pattern = re.compile('%s($|\?|/).*' % handler_name)
158 return pattern.match(self.path)
159
initial.commit94958cf2008-07-26 22:42:52 +0000160 def GetMIMETypeFromName(self, file_name):
161 """Returns the mime type for the specified file_name. So far it only looks
162 at the file extension."""
163
164 (shortname, extension) = os.path.splitext(file_name)
165 if len(extension) == 0:
166 # no extension.
167 return self._default_mime_type
168
ericroman@google.comc17ca532009-05-07 03:51:05 +0000169 # extension starts with a dot, so we need to remove it
170 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000171
172 def KillHandler(self):
173 """This request handler kills the server, for use when we're done"
174 with the a particular test."""
175
176 if (self.path.find("kill") < 0):
177 return False
178
179 self.send_response(200)
180 self.send_header('Content-type', 'text/html')
181 self.send_header('Cache-Control', 'max-age=0')
182 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000183 if options.never_die:
184 self.wfile.write('I cannot die!! BWAHAHA')
185 else:
186 self.wfile.write('Goodbye cruel world!')
187 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000188
189 return True
190
191 def NoCacheMaxAgeTimeHandler(self):
192 """This request handler yields a page with the title set to the current
193 system time, and no caching requested."""
194
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000195 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000196 return False
197
198 self.send_response(200)
199 self.send_header('Cache-Control', 'max-age=0')
200 self.send_header('Content-type', 'text/html')
201 self.end_headers()
202
maruel@google.come250a9b2009-03-10 17:39:46 +0000203 self.wfile.write('<html><head><title>%s</title></head></html>' %
204 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000205
206 return True
207
208 def NoCacheTimeHandler(self):
209 """This request handler yields a page with the title set to the current
210 system time, and no caching requested."""
211
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000212 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000213 return False
214
215 self.send_response(200)
216 self.send_header('Cache-Control', 'no-cache')
217 self.send_header('Content-type', 'text/html')
218 self.end_headers()
219
maruel@google.come250a9b2009-03-10 17:39:46 +0000220 self.wfile.write('<html><head><title>%s</title></head></html>' %
221 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000222
223 return True
224
225 def CacheTimeHandler(self):
226 """This request handler yields a page with the title set to the current
227 system time, and allows caching for one minute."""
228
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000229 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000230 return False
231
232 self.send_response(200)
233 self.send_header('Cache-Control', 'max-age=60')
234 self.send_header('Content-type', 'text/html')
235 self.end_headers()
236
maruel@google.come250a9b2009-03-10 17:39:46 +0000237 self.wfile.write('<html><head><title>%s</title></head></html>' %
238 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000239
240 return True
241
242 def CacheExpiresHandler(self):
243 """This request handler yields a page with the title set to the current
244 system time, and set the page to expire on 1 Jan 2099."""
245
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000246 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000247 return False
248
249 self.send_response(200)
250 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
251 self.send_header('Content-type', 'text/html')
252 self.end_headers()
253
maruel@google.come250a9b2009-03-10 17:39:46 +0000254 self.wfile.write('<html><head><title>%s</title></head></html>' %
255 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000256
257 return True
258
259 def CacheProxyRevalidateHandler(self):
260 """This request handler yields a page with the title set to the current
261 system time, and allows caching for 60 seconds"""
262
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000263 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000264 return False
265
266 self.send_response(200)
267 self.send_header('Content-type', 'text/html')
268 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
269 self.end_headers()
270
maruel@google.come250a9b2009-03-10 17:39:46 +0000271 self.wfile.write('<html><head><title>%s</title></head></html>' %
272 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000273
274 return True
275
276 def CachePrivateHandler(self):
277 """This request handler yields a page with the title set to the current
278 system time, and allows caching for 5 seconds."""
279
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000280 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000281 return False
282
283 self.send_response(200)
284 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000285 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000286 self.end_headers()
287
maruel@google.come250a9b2009-03-10 17:39:46 +0000288 self.wfile.write('<html><head><title>%s</title></head></html>' %
289 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000290
291 return True
292
293 def CachePublicHandler(self):
294 """This request handler yields a page with the title set to the current
295 system time, and allows caching for 5 seconds."""
296
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000297 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000298 return False
299
300 self.send_response(200)
301 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000302 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000303 self.end_headers()
304
maruel@google.come250a9b2009-03-10 17:39:46 +0000305 self.wfile.write('<html><head><title>%s</title></head></html>' %
306 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000307
308 return True
309
310 def CacheSMaxAgeHandler(self):
311 """This request handler yields a page with the title set to the current
312 system time, and does not allow for caching."""
313
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000314 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000315 return False
316
317 self.send_response(200)
318 self.send_header('Content-type', 'text/html')
319 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
320 self.end_headers()
321
maruel@google.come250a9b2009-03-10 17:39:46 +0000322 self.wfile.write('<html><head><title>%s</title></head></html>' %
323 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000324
325 return True
326
327 def CacheMustRevalidateHandler(self):
328 """This request handler yields a page with the title set to the current
329 system time, and does not allow caching."""
330
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000331 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000332 return False
333
334 self.send_response(200)
335 self.send_header('Content-type', 'text/html')
336 self.send_header('Cache-Control', 'must-revalidate')
337 self.end_headers()
338
maruel@google.come250a9b2009-03-10 17:39:46 +0000339 self.wfile.write('<html><head><title>%s</title></head></html>' %
340 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000341
342 return True
343
344 def CacheMustRevalidateMaxAgeHandler(self):
345 """This request handler yields a page with the title set to the current
346 system time, and does not allow caching event though max-age of 60
347 seconds is specified."""
348
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000349 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000350 return False
351
352 self.send_response(200)
353 self.send_header('Content-type', 'text/html')
354 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
355 self.end_headers()
356
maruel@google.come250a9b2009-03-10 17:39:46 +0000357 self.wfile.write('<html><head><title>%s</title></head></html>' %
358 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000359
360 return True
361
initial.commit94958cf2008-07-26 22:42:52 +0000362 def CacheNoStoreHandler(self):
363 """This request handler yields a page with the title set to the current
364 system time, and does not allow the page to be stored."""
365
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000366 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000367 return False
368
369 self.send_response(200)
370 self.send_header('Content-type', 'text/html')
371 self.send_header('Cache-Control', 'no-store')
372 self.end_headers()
373
maruel@google.come250a9b2009-03-10 17:39:46 +0000374 self.wfile.write('<html><head><title>%s</title></head></html>' %
375 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000376
377 return True
378
379 def CacheNoStoreMaxAgeHandler(self):
380 """This request handler yields a page with the title set to the current
381 system time, and does not allow the page to be stored even though max-age
382 of 60 seconds is specified."""
383
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000385 return False
386
387 self.send_response(200)
388 self.send_header('Content-type', 'text/html')
389 self.send_header('Cache-Control', 'max-age=60, no-store')
390 self.end_headers()
391
maruel@google.come250a9b2009-03-10 17:39:46 +0000392 self.wfile.write('<html><head><title>%s</title></head></html>' %
393 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000394
395 return True
396
397
398 def CacheNoTransformHandler(self):
399 """This request handler yields a page with the title set to the current
400 system time, and does not allow the content to transformed during
401 user-agent caching"""
402
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000403 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000404 return False
405
406 self.send_response(200)
407 self.send_header('Content-type', 'text/html')
408 self.send_header('Cache-Control', 'no-transform')
409 self.end_headers()
410
maruel@google.come250a9b2009-03-10 17:39:46 +0000411 self.wfile.write('<html><head><title>%s</title></head></html>' %
412 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000413
414 return True
415
416 def EchoHeader(self):
417 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000418 """The only difference between this function and the EchoHeaderOverride"""
419 """function is in the parameter being passed to the helper function"""
420 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000421
ananta@chromium.org219b2062009-10-23 16:09:41 +0000422 def EchoHeaderOverride(self):
423 """This handler echoes back the value of a specific request header."""
424 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
425 """IE to issue HTTP requests using the host network stack."""
426 """The Accept and Charset tests which expect the server to echo back"""
427 """the corresponding headers fail here as IE returns cached responses"""
428 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
429 """treats this request as a new request and does not cache it."""
430 return self.EchoHeaderHelper("/echoheaderoverride")
431
432 def EchoHeaderHelper(self, echo_header):
433 """This function echoes back the value of the request header passed in."""
434 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000435 return False
436
437 query_char = self.path.find('?')
438 if query_char != -1:
439 header_name = self.path[query_char+1:]
440
441 self.send_response(200)
442 self.send_header('Content-type', 'text/plain')
443 self.send_header('Cache-control', 'max-age=60000')
444 # insert a vary header to properly indicate that the cachability of this
445 # request is subject to value of the request header being echoed.
446 if len(header_name) > 0:
447 self.send_header('Vary', header_name)
448 self.end_headers()
449
450 if len(header_name) > 0:
451 self.wfile.write(self.headers.getheader(header_name))
452
453 return True
454
455 def EchoHandler(self):
456 """This handler just echoes back the payload of the request, for testing
457 form submission."""
458
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000459 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000460 return False
461
462 self.send_response(200)
463 self.send_header('Content-type', 'text/html')
464 self.end_headers()
465 length = int(self.headers.getheader('content-length'))
466 request = self.rfile.read(length)
467 self.wfile.write(request)
468 return True
469
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000470 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000471 """This is handler dumps the content of POST/PUT request to a disk file
472 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000473
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000474 prefix='/writefile/'
475 if not self.path.startswith(prefix):
476 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000477
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000478 file_name = self.path[len(prefix):]
479
480 # do not allow fancy chars in file name
481 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
482 if len(file_name) and file_name[0] != '.':
483 path = os.path.join(self.server.data_dir, 'dump', file_name);
484 length = int(self.headers.getheader('content-length'))
485 request = self.rfile.read(length)
486 f = open(path, "wb")
487 f.write(request);
488 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000489
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000490 self.send_response(200)
491 self.send_header('Content-type', 'text/html')
492 self.end_headers()
493 self.wfile.write('<html>%s</html>' % file_name)
494 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000495
initial.commit94958cf2008-07-26 22:42:52 +0000496 def EchoTitleHandler(self):
497 """This handler is like Echo, but sets the page title to the request."""
498
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000499 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000500 return False
501
502 self.send_response(200)
503 self.send_header('Content-type', 'text/html')
504 self.end_headers()
505 length = int(self.headers.getheader('content-length'))
506 request = self.rfile.read(length)
507 self.wfile.write('<html><head><title>')
508 self.wfile.write(request)
509 self.wfile.write('</title></head></html>')
510 return True
511
512 def EchoAllHandler(self):
513 """This handler yields a (more) human-readable page listing information
514 about the request header & contents."""
515
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000516 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000517 return False
518
519 self.send_response(200)
520 self.send_header('Content-type', 'text/html')
521 self.end_headers()
522 self.wfile.write('<html><head><style>'
523 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
524 '</style></head><body>'
525 '<div style="float: right">'
526 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
527 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000528
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000529 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000530 length = int(self.headers.getheader('content-length'))
531 qs = self.rfile.read(length)
532 params = cgi.parse_qs(qs, keep_blank_values=1)
533
534 for param in params:
535 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000536
537 self.wfile.write('</pre>')
538
539 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
540
541 self.wfile.write('</body></html>')
542 return True
543
544 def DownloadHandler(self):
545 """This handler sends a downloadable file with or without reporting
546 the size (6K)."""
547
548 if self.path.startswith("/download-unknown-size"):
549 send_length = False
550 elif self.path.startswith("/download-known-size"):
551 send_length = True
552 else:
553 return False
554
555 #
556 # The test which uses this functionality is attempting to send
557 # small chunks of data to the client. Use a fairly large buffer
558 # so that we'll fill chrome's IO buffer enough to force it to
559 # actually write the data.
560 # See also the comments in the client-side of this test in
561 # download_uitest.cc
562 #
563 size_chunk1 = 35*1024
564 size_chunk2 = 10*1024
565
566 self.send_response(200)
567 self.send_header('Content-type', 'application/octet-stream')
568 self.send_header('Cache-Control', 'max-age=0')
569 if send_length:
570 self.send_header('Content-Length', size_chunk1 + size_chunk2)
571 self.end_headers()
572
573 # First chunk of data:
574 self.wfile.write("*" * size_chunk1)
575 self.wfile.flush()
576
577 # handle requests until one of them clears this flag.
578 self.server.waitForDownload = True
579 while self.server.waitForDownload:
580 self.server.handle_request()
581
582 # Second chunk of data:
583 self.wfile.write("*" * size_chunk2)
584 return True
585
586 def DownloadFinishHandler(self):
587 """This handler just tells the server to finish the current download."""
588
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000589 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000590 return False
591
592 self.server.waitForDownload = False
593 self.send_response(200)
594 self.send_header('Content-type', 'text/html')
595 self.send_header('Cache-Control', 'max-age=0')
596 self.end_headers()
597 return True
598
599 def FileHandler(self):
600 """This handler sends the contents of the requested file. Wow, it's like
601 a real webserver!"""
602
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000603 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000604 if not self.path.startswith(prefix):
605 return False
606
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000607 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000608 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000609 self.rfile.read(int(self.headers.getheader('content-length')))
610
initial.commit94958cf2008-07-26 22:42:52 +0000611 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000612 if file.find('?') > -1:
613 # Ignore the query parameters entirely.
614 url, querystring = file.split('?')
615 else:
616 url = file
617 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000618 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000619 if os.path.isdir(path):
620 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000621
622 if not os.path.isfile(path):
623 print "File not found " + file + " full path:" + path
624 self.send_error(404)
625 return True
626
627 f = open(path, "rb")
628 data = f.read()
629 f.close()
630
631 # If file.mock-http-headers exists, it contains the headers we
632 # should send. Read them in and parse them.
633 headers_path = path + '.mock-http-headers'
634 if os.path.isfile(headers_path):
635 f = open(headers_path, "r")
636
637 # "HTTP/1.1 200 OK"
638 response = f.readline()
639 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
640 self.send_response(int(status_code))
641
642 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000643 header_values = re.findall('(\S+):\s*(.*)', line)
644 if len(header_values) > 0:
645 # "name: value"
646 name, value = header_values[0]
647 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000648 f.close()
649 else:
650 # Could be more generic once we support mime-type sniffing, but for
651 # now we need to set it explicitly.
652 self.send_response(200)
653 self.send_header('Content-type', self.GetMIMETypeFromName(file))
654 self.send_header('Content-Length', len(data))
655 self.end_headers()
656
657 self.wfile.write(data)
658
659 return True
660
661 def RealFileWithCommonHeaderHandler(self):
662 """This handler sends the contents of the requested file without the pseudo
663 http head!"""
664
665 prefix='/realfiles/'
666 if not self.path.startswith(prefix):
667 return False
668
669 file = self.path[len(prefix):]
670 path = os.path.join(self.server.data_dir, file)
671
672 try:
673 f = open(path, "rb")
674 data = f.read()
675 f.close()
676
677 # just simply set the MIME as octal stream
678 self.send_response(200)
679 self.send_header('Content-type', 'application/octet-stream')
680 self.end_headers()
681
682 self.wfile.write(data)
683 except:
684 self.send_error(404)
685
686 return True
687
688 def RealBZ2FileWithCommonHeaderHandler(self):
689 """This handler sends the bzip2 contents of the requested file with
690 corresponding Content-Encoding field in http head!"""
691
692 prefix='/realbz2files/'
693 if not self.path.startswith(prefix):
694 return False
695
696 parts = self.path.split('?')
697 file = parts[0][len(prefix):]
698 path = os.path.join(self.server.data_dir, file) + '.bz2'
699
700 if len(parts) > 1:
701 options = parts[1]
702 else:
703 options = ''
704
705 try:
706 self.send_response(200)
707 accept_encoding = self.headers.get("Accept-Encoding")
708 if accept_encoding.find("bzip2") != -1:
709 f = open(path, "rb")
710 data = f.read()
711 f.close()
712 self.send_header('Content-Encoding', 'bzip2')
713 self.send_header('Content-type', 'application/x-bzip2')
714 self.end_headers()
715 if options == 'incremental-header':
716 self.wfile.write(data[:1])
717 self.wfile.flush()
718 time.sleep(1.0)
719 self.wfile.write(data[1:])
720 else:
721 self.wfile.write(data)
722 else:
723 """client do not support bzip2 format, send pseudo content
724 """
725 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
726 self.end_headers()
727 self.wfile.write("you do not support bzip2 encoding")
728 except:
729 self.send_error(404)
730
731 return True
732
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000733 def SetCookieHandler(self):
734 """This handler just sets a cookie, for testing cookie handling."""
735
736 if not self._ShouldHandleRequest("/set-cookie"):
737 return False
738
739 query_char = self.path.find('?')
740 if query_char != -1:
741 cookie_values = self.path[query_char + 1:].split('&')
742 else:
743 cookie_values = ("",)
744 self.send_response(200)
745 self.send_header('Content-type', 'text/html')
746 for cookie_value in cookie_values:
747 self.send_header('Set-Cookie', '%s' % cookie_value)
748 self.end_headers()
749 for cookie_value in cookie_values:
750 self.wfile.write('%s' % cookie_value)
751 return True
752
initial.commit94958cf2008-07-26 22:42:52 +0000753 def AuthBasicHandler(self):
754 """This handler tests 'Basic' authentication. It just sends a page with
755 title 'user/pass' if you succeed."""
756
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000757 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000758 return False
759
760 username = userpass = password = b64str = ""
761
ericroman@google.com239b4d82009-03-27 04:00:22 +0000762 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
763
initial.commit94958cf2008-07-26 22:42:52 +0000764 auth = self.headers.getheader('authorization')
765 try:
766 if not auth:
767 raise Exception('no auth')
768 b64str = re.findall(r'Basic (\S+)', auth)[0]
769 userpass = base64.b64decode(b64str)
770 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
771 if password != 'secret':
772 raise Exception('wrong password')
773 except Exception, e:
774 # Authentication failed.
775 self.send_response(401)
776 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
777 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000778 if set_cookie_if_challenged:
779 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000780 self.end_headers()
781 self.wfile.write('<html><head>')
782 self.wfile.write('<title>Denied: %s</title>' % e)
783 self.wfile.write('</head><body>')
784 self.wfile.write('auth=%s<p>' % auth)
785 self.wfile.write('b64str=%s<p>' % b64str)
786 self.wfile.write('username: %s<p>' % username)
787 self.wfile.write('userpass: %s<p>' % userpass)
788 self.wfile.write('password: %s<p>' % password)
789 self.wfile.write('You sent:<br>%s<p>' % self.headers)
790 self.wfile.write('</body></html>')
791 return True
792
793 # Authentication successful. (Return a cachable response to allow for
794 # testing cached pages that require authentication.)
795 if_none_match = self.headers.getheader('if-none-match')
796 if if_none_match == "abc":
797 self.send_response(304)
798 self.end_headers()
799 else:
800 self.send_response(200)
801 self.send_header('Content-type', 'text/html')
802 self.send_header('Cache-control', 'max-age=60000')
803 self.send_header('Etag', 'abc')
804 self.end_headers()
805 self.wfile.write('<html><head>')
806 self.wfile.write('<title>%s/%s</title>' % (username, password))
807 self.wfile.write('</head><body>')
808 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000809 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.wfile.write('</body></html>')
811
812 return True
813
tonyg@chromium.org75054202010-03-31 22:06:10 +0000814 def GetNonce(self, force_reset=False):
815 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000816
tonyg@chromium.org75054202010-03-31 22:06:10 +0000817 This is a fake implementation. A real implementation would only use a given
818 nonce a single time (hence the name n-once). However, for the purposes of
819 unittesting, we don't care about the security of the nonce.
820
821 Args:
822 force_reset: Iff set, the nonce will be changed. Useful for testing the
823 "stale" response.
824 """
825 if force_reset or not self.server.nonce_time:
826 self.server.nonce_time = time.time()
827 return _new_md5('privatekey%s%d' %
828 (self.path, self.server.nonce_time)).hexdigest()
829
830 def AuthDigestHandler(self):
831 """This handler tests 'Digest' authentication.
832
833 It just sends a page with title 'user/pass' if you succeed.
834
835 A stale response is sent iff "stale" is present in the request path.
836 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000837 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000838 return False
839
tonyg@chromium.org75054202010-03-31 22:06:10 +0000840 stale = 'stale' in self.path
841 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000842 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000843 password = 'secret'
844 realm = 'testrealm'
845
846 auth = self.headers.getheader('authorization')
847 pairs = {}
848 try:
849 if not auth:
850 raise Exception('no auth')
851 if not auth.startswith('Digest'):
852 raise Exception('not digest')
853 # Pull out all the name="value" pairs as a dictionary.
854 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
855
856 # Make sure it's all valid.
857 if pairs['nonce'] != nonce:
858 raise Exception('wrong nonce')
859 if pairs['opaque'] != opaque:
860 raise Exception('wrong opaque')
861
862 # Check the 'response' value and make sure it matches our magic hash.
863 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000864 hash_a1 = _new_md5(
865 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000866 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000867 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000868 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000869 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
870 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000871 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000872
873 if pairs['response'] != response:
874 raise Exception('wrong password')
875 except Exception, e:
876 # Authentication failed.
877 self.send_response(401)
878 hdr = ('Digest '
879 'realm="%s", '
880 'domain="/", '
881 'qop="auth", '
882 'algorithm=MD5, '
883 'nonce="%s", '
884 'opaque="%s"') % (realm, nonce, opaque)
885 if stale:
886 hdr += ', stale="TRUE"'
887 self.send_header('WWW-Authenticate', hdr)
888 self.send_header('Content-type', 'text/html')
889 self.end_headers()
890 self.wfile.write('<html><head>')
891 self.wfile.write('<title>Denied: %s</title>' % e)
892 self.wfile.write('</head><body>')
893 self.wfile.write('auth=%s<p>' % auth)
894 self.wfile.write('pairs=%s<p>' % pairs)
895 self.wfile.write('You sent:<br>%s<p>' % self.headers)
896 self.wfile.write('We are replying:<br>%s<p>' % hdr)
897 self.wfile.write('</body></html>')
898 return True
899
900 # Authentication successful.
901 self.send_response(200)
902 self.send_header('Content-type', 'text/html')
903 self.end_headers()
904 self.wfile.write('<html><head>')
905 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
906 self.wfile.write('</head><body>')
907 self.wfile.write('auth=%s<p>' % auth)
908 self.wfile.write('pairs=%s<p>' % pairs)
909 self.wfile.write('</body></html>')
910
911 return True
912
913 def SlowServerHandler(self):
914 """Wait for the user suggested time before responding. The syntax is
915 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000916 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000917 return False
918 query_char = self.path.find('?')
919 wait_sec = 1.0
920 if query_char >= 0:
921 try:
922 wait_sec = int(self.path[query_char + 1:])
923 except ValueError:
924 pass
925 time.sleep(wait_sec)
926 self.send_response(200)
927 self.send_header('Content-type', 'text/plain')
928 self.end_headers()
929 self.wfile.write("waited %d seconds" % wait_sec)
930 return True
931
932 def ContentTypeHandler(self):
933 """Returns a string of html with the given content type. E.g.,
934 /contenttype?text/css returns an html file with the Content-Type
935 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000936 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000937 return False
938 query_char = self.path.find('?')
939 content_type = self.path[query_char + 1:].strip()
940 if not content_type:
941 content_type = 'text/html'
942 self.send_response(200)
943 self.send_header('Content-Type', content_type)
944 self.end_headers()
945 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
946 return True
947
948 def ServerRedirectHandler(self):
949 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000950 '/server-redirect?http://foo.bar/asdf' to redirect to
951 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000952
953 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000954 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000955 return False
956
957 query_char = self.path.find('?')
958 if query_char < 0 or len(self.path) <= query_char + 1:
959 self.sendRedirectHelp(test_name)
960 return True
961 dest = self.path[query_char + 1:]
962
963 self.send_response(301) # moved permanently
964 self.send_header('Location', dest)
965 self.send_header('Content-type', 'text/html')
966 self.end_headers()
967 self.wfile.write('<html><head>')
968 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
969
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000970 return True
initial.commit94958cf2008-07-26 22:42:52 +0000971
972 def ClientRedirectHandler(self):
973 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000974 '/client-redirect?http://foo.bar/asdf' to redirect to
975 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000976
977 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000978 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000979 return False
980
981 query_char = self.path.find('?');
982 if query_char < 0 or len(self.path) <= query_char + 1:
983 self.sendRedirectHelp(test_name)
984 return True
985 dest = self.path[query_char + 1:]
986
987 self.send_response(200)
988 self.send_header('Content-type', 'text/html')
989 self.end_headers()
990 self.wfile.write('<html><head>')
991 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
992 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
993
994 return True
995
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000996 def ChromiumSyncTimeHandler(self):
997 """Handle Chromium sync .../time requests.
998
999 The syncer sometimes checks server reachability by examining /time.
1000 """
1001 test_name = "/chromiumsync/time"
1002 if not self._ShouldHandleRequest(test_name):
1003 return False
1004
1005 self.send_response(200)
1006 self.send_header('Content-type', 'text/html')
1007 self.end_headers()
1008 return True
1009
1010 def ChromiumSyncCommandHandler(self):
1011 """Handle a chromiumsync command arriving via http.
1012
1013 This covers all sync protocol commands: authentication, getupdates, and
1014 commit.
1015 """
1016 test_name = "/chromiumsync/command"
1017 if not self._ShouldHandleRequest(test_name):
1018 return False
1019
1020 length = int(self.headers.getheader('content-length'))
1021 raw_request = self.rfile.read(length)
1022
pathorn@chromium.org44920122010-07-27 18:25:35 +00001023 if not self.server._sync_handler:
1024 import chromiumsync
1025 self.server._sync_handler = chromiumsync.TestServer()
1026 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1027 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001028 self.send_response(http_response)
1029 self.end_headers()
1030 self.wfile.write(raw_reply)
1031 return True
1032
tony@chromium.org03266982010-03-05 03:18:42 +00001033 def MultipartHandler(self):
1034 """Send a multipart response (10 text/html pages)."""
1035 test_name = "/multipart"
1036 if not self._ShouldHandleRequest(test_name):
1037 return False
1038
1039 num_frames = 10
1040 bound = '12345'
1041 self.send_response(200)
1042 self.send_header('Content-type',
1043 'multipart/x-mixed-replace;boundary=' + bound)
1044 self.end_headers()
1045
1046 for i in xrange(num_frames):
1047 self.wfile.write('--' + bound + '\r\n')
1048 self.wfile.write('Content-type: text/html\r\n\r\n')
1049 self.wfile.write('<title>page ' + str(i) + '</title>')
1050 self.wfile.write('page ' + str(i))
1051
1052 self.wfile.write('--' + bound + '--')
1053 return True
1054
initial.commit94958cf2008-07-26 22:42:52 +00001055 def DefaultResponseHandler(self):
1056 """This is the catch-all response handler for requests that aren't handled
1057 by one of the special handlers above.
1058 Note that we specify the content-length as without it the https connection
1059 is not closed properly (and the browser keeps expecting data)."""
1060
1061 contents = "Default response given for path: " + self.path
1062 self.send_response(200)
1063 self.send_header('Content-type', 'text/html')
1064 self.send_header("Content-Length", len(contents))
1065 self.end_headers()
1066 self.wfile.write(contents)
1067 return True
1068
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001069 def RedirectConnectHandler(self):
1070 """Sends a redirect to the CONNECT request for www.redirect.com. This
1071 response is not specified by the RFC, so the browser should not follow
1072 the redirect."""
1073
1074 if (self.path.find("www.redirect.com") < 0):
1075 return False
1076
1077 dest = "http://www.destination.com/foo.js"
1078
1079 self.send_response(302) # moved temporarily
1080 self.send_header('Location', dest)
1081 self.send_header('Connection', 'close')
1082 self.end_headers()
1083 return True
1084
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001085 def ServerAuthConnectHandler(self):
1086 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1087 response doesn't make sense because the proxy server cannot request
1088 server authentication."""
1089
1090 if (self.path.find("www.server-auth.com") < 0):
1091 return False
1092
1093 challenge = 'Basic realm="WallyWorld"'
1094
1095 self.send_response(401) # unauthorized
1096 self.send_header('WWW-Authenticate', challenge)
1097 self.send_header('Connection', 'close')
1098 self.end_headers()
1099 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001100
1101 def DefaultConnectResponseHandler(self):
1102 """This is the catch-all response handler for CONNECT requests that aren't
1103 handled by one of the special handlers above. Real Web servers respond
1104 with 400 to CONNECT requests."""
1105
1106 contents = "Your client has issued a malformed or illegal request."
1107 self.send_response(400) # bad request
1108 self.send_header('Content-type', 'text/html')
1109 self.send_header("Content-Length", len(contents))
1110 self.end_headers()
1111 self.wfile.write(contents)
1112 return True
1113
1114 def do_CONNECT(self):
1115 for handler in self._connect_handlers:
1116 if handler():
1117 return
1118
initial.commit94958cf2008-07-26 22:42:52 +00001119 def do_GET(self):
1120 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001121 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001122 return
1123
1124 def do_POST(self):
1125 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001126 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001127 return
1128
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001129 def do_PUT(self):
1130 for handler in self._put_handlers:
1131 if handler():
1132 return
1133
initial.commit94958cf2008-07-26 22:42:52 +00001134 # called by the redirect handling function when there is no parameter
1135 def sendRedirectHelp(self, redirect_name):
1136 self.send_response(200)
1137 self.send_header('Content-type', 'text/html')
1138 self.end_headers()
1139 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1140 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1141 self.wfile.write('</body></html>')
1142
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001143def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001144 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1145 requests will be stored. If the directory already exists all files and
1146 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001147 dump_dir = os.path.join(data_dir, 'dump');
1148 if os.path.isdir(dump_dir):
1149 shutil.rmtree(dump_dir)
1150 os.mkdir(dump_dir)
1151
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001152def MakeDataDir():
1153 if options.data_dir:
1154 if not os.path.isdir(options.data_dir):
1155 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1156 return None
1157 my_data_dir = options.data_dir
1158 else:
1159 # Create the default path to our data dir, relative to the exe dir.
1160 my_data_dir = os.path.dirname(sys.argv[0])
1161 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001162 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001163
1164 #TODO(ibrar): Must use Find* funtion defined in google\tools
1165 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1166
1167 return my_data_dir
1168
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001169def TryKillingOldServer(port):
1170 # Note that an HTTP /kill request to the FTP server has the effect of
1171 # killing it.
1172 for protocol in ["http", "https"]:
1173 try:
1174 urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
1175 print "Killed old server instance on port %d (via %s)" % (port, protocol)
1176 except urllib2.URLError:
1177 # Common case, indicates no server running.
1178 pass
1179
initial.commit94958cf2008-07-26 22:42:52 +00001180def main(options, args):
1181 # redirect output to a log file so it doesn't spam the unit test output
1182 logfile = open('testserver.log', 'w')
1183 sys.stderr = sys.stdout = logfile
1184
1185 port = options.port
1186
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001187 # Try to free up the port if there's an orphaned old instance.
1188 TryKillingOldServer(port)
1189
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001190 if options.server_type == SERVER_HTTP:
1191 if options.cert:
1192 # let's make sure the cert file exists.
1193 if not os.path.isfile(options.cert):
1194 print 'specified cert file not found: ' + options.cert + ' exiting...'
1195 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001196 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
1197 options.ssl_client_auth)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001198 print 'HTTPS server started on port %d...' % port
1199 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001200 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001201 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001202
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001203 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001204 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001205 server._sync_handler = None
1206
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001207 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001208
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001209 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001210 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001211 my_data_dir = MakeDataDir()
1212
1213 def line_logger(msg):
1214 if (msg.find("kill") >= 0):
1215 server.stop = True
1216 print 'shutting down server'
1217 sys.exit(0)
1218
1219 # Instantiate a dummy authorizer for managing 'virtual' users
1220 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1221
1222 # Define a new user having full r/w permissions and a read-only
1223 # anonymous user
1224 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1225
1226 authorizer.add_anonymous(my_data_dir)
1227
1228 # Instantiate FTP handler class
1229 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1230 ftp_handler.authorizer = authorizer
1231 pyftpdlib.ftpserver.logline = line_logger
1232
1233 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001234 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1235 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001236
1237 # Instantiate FTP server class and listen to 127.0.0.1:port
1238 address = ('127.0.0.1', port)
1239 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1240 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001241
1242 try:
1243 server.serve_forever()
1244 except KeyboardInterrupt:
1245 print 'shutting down server'
1246 server.stop = True
1247
1248if __name__ == '__main__':
1249 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001250 option_parser.add_option("-f", '--ftp', action='store_const',
1251 const=SERVER_FTP, default=SERVER_HTTP,
1252 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001253 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001254 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001255 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001256 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001257 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001258 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001259 help='Specify that https should be used, specify '
1260 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001261 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001262 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1263 help='Require SSL client auth on every connection.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001264 option_parser.add_option('', '--file-root-url', default='/files/',
1265 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001266 option_parser.add_option('', '--never-die', default=False,
1267 action="store_true",
1268 help='Prevent the server from dying when visiting '
1269 'a /kill URL. Useful for manually running some '
1270 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001271 options, args = option_parser.parse_args()
1272
1273 sys.exit(main(options, args))