blob: 099f351c8942829d256da55e891b7b21498510d0 [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
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000026import warnings
27
28# Ignore deprecation warnings, they make our output more cluttered.
29warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000030
31import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000032import tlslite
33import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000034
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000035try:
36 import hashlib
37 _new_md5 = hashlib.md5
38except ImportError:
39 import md5
40 _new_md5 = md5.new
41
maruel@chromium.org756cf982009-03-05 12:46:38 +000042SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000043SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000044
45debug_output = sys.stderr
46def debug(str):
47 debug_output.write(str + "\n")
48 debug_output.flush()
49
50class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
51 """This is a specialization of of BaseHTTPServer to allow it
52 to be exited cleanly (by setting its "stop" member to True)."""
53
54 def serve_forever(self):
55 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000056 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000057 while not self.stop:
58 self.handle_request()
59 self.socket.close()
60
61class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
62 """This is a specialization of StoppableHTTPerver that add https support."""
63
davidben@chromium.org31282a12010-08-07 01:10:02 +000064 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000065 ssl_client_auth, ssl_client_cas):
initial.commit94958cf2008-07-26 22:42:52 +000066 s = open(cert_path).read()
67 x509 = tlslite.api.X509()
68 x509.parse(s)
69 self.cert_chain = tlslite.api.X509CertChain([x509])
70 s = open(cert_path).read()
71 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000072 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000073 self.ssl_client_cas = []
74 for ca_file in ssl_client_cas:
75 s = open(ca_file).read()
76 x509 = tlslite.api.X509()
77 x509.parse(s)
78 self.ssl_client_cas.append(x509.subject)
initial.commit94958cf2008-07-26 22:42:52 +000079
80 self.session_cache = tlslite.api.SessionCache()
81 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
82
83 def handshake(self, tlsConnection):
84 """Creates the SSL connection."""
85 try:
86 tlsConnection.handshakeServer(certChain=self.cert_chain,
87 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000088 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000089 reqCert=self.ssl_client_auth,
90 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +000091 tlsConnection.ignoreAbruptClose = True
92 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000093 except tlslite.api.TLSAbruptCloseError:
94 # Ignore abrupt close.
95 return True
initial.commit94958cf2008-07-26 22:42:52 +000096 except tlslite.api.TLSError, error:
97 print "Handshake failure:", str(error)
98 return False
99
100class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
101
102 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000103 self._connect_handlers = [
104 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000105 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000106 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000107 self._get_handlers = [
108 self.KillHandler,
109 self.NoCacheMaxAgeTimeHandler,
110 self.NoCacheTimeHandler,
111 self.CacheTimeHandler,
112 self.CacheExpiresHandler,
113 self.CacheProxyRevalidateHandler,
114 self.CachePrivateHandler,
115 self.CachePublicHandler,
116 self.CacheSMaxAgeHandler,
117 self.CacheMustRevalidateHandler,
118 self.CacheMustRevalidateMaxAgeHandler,
119 self.CacheNoStoreHandler,
120 self.CacheNoStoreMaxAgeHandler,
121 self.CacheNoTransformHandler,
122 self.DownloadHandler,
123 self.DownloadFinishHandler,
124 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000125 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000126 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000127 self.FileHandler,
128 self.RealFileWithCommonHeaderHandler,
129 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000130 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000131 self.AuthBasicHandler,
132 self.AuthDigestHandler,
133 self.SlowServerHandler,
134 self.ContentTypeHandler,
135 self.ServerRedirectHandler,
136 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000137 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000138 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000139 self.DefaultResponseHandler]
140 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000141 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000142 self.EchoTitleHandler,
143 self.EchoAllHandler,
skrul@chromium.orgfff501f2010-08-13 21:01:52 +0000144 self.ChromiumSyncConfigureHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000145 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000146 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000147 self._put_handlers = [
148 self.WriteFile,
149 self.EchoTitleHandler,
150 self.EchoAllHandler,
151 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000152
maruel@google.come250a9b2009-03-10 17:39:46 +0000153 self._mime_types = {
154 'gif': 'image/gif',
155 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000156 'jpg' : 'image/jpeg',
157 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000158 }
initial.commit94958cf2008-07-26 22:42:52 +0000159 self._default_mime_type = 'text/html'
160
maruel@google.come250a9b2009-03-10 17:39:46 +0000161 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
162 client_address,
163 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000164
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000165 def log_request(self, *args, **kwargs):
166 # Disable request logging to declutter test log output.
167 pass
168
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000169 def _ShouldHandleRequest(self, handler_name):
170 """Determines if the path can be handled by the handler.
171
172 We consider a handler valid if the path begins with the
173 handler name. It can optionally be followed by "?*", "/*".
174 """
175
176 pattern = re.compile('%s($|\?|/).*' % handler_name)
177 return pattern.match(self.path)
178
initial.commit94958cf2008-07-26 22:42:52 +0000179 def GetMIMETypeFromName(self, file_name):
180 """Returns the mime type for the specified file_name. So far it only looks
181 at the file extension."""
182
183 (shortname, extension) = os.path.splitext(file_name)
184 if len(extension) == 0:
185 # no extension.
186 return self._default_mime_type
187
ericroman@google.comc17ca532009-05-07 03:51:05 +0000188 # extension starts with a dot, so we need to remove it
189 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000190
191 def KillHandler(self):
192 """This request handler kills the server, for use when we're done"
193 with the a particular test."""
194
195 if (self.path.find("kill") < 0):
196 return False
197
198 self.send_response(200)
199 self.send_header('Content-type', 'text/html')
200 self.send_header('Cache-Control', 'max-age=0')
201 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000202 if options.never_die:
203 self.wfile.write('I cannot die!! BWAHAHA')
204 else:
205 self.wfile.write('Goodbye cruel world!')
206 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000207
208 return True
209
210 def NoCacheMaxAgeTimeHandler(self):
211 """This request handler yields a page with the title set to the current
212 system time, and no caching requested."""
213
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000214 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000215 return False
216
217 self.send_response(200)
218 self.send_header('Cache-Control', 'max-age=0')
219 self.send_header('Content-type', 'text/html')
220 self.end_headers()
221
maruel@google.come250a9b2009-03-10 17:39:46 +0000222 self.wfile.write('<html><head><title>%s</title></head></html>' %
223 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000224
225 return True
226
227 def NoCacheTimeHandler(self):
228 """This request handler yields a page with the title set to the current
229 system time, and no caching requested."""
230
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000231 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000232 return False
233
234 self.send_response(200)
235 self.send_header('Cache-Control', 'no-cache')
236 self.send_header('Content-type', 'text/html')
237 self.end_headers()
238
maruel@google.come250a9b2009-03-10 17:39:46 +0000239 self.wfile.write('<html><head><title>%s</title></head></html>' %
240 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000241
242 return True
243
244 def CacheTimeHandler(self):
245 """This request handler yields a page with the title set to the current
246 system time, and allows caching for one minute."""
247
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000248 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000249 return False
250
251 self.send_response(200)
252 self.send_header('Cache-Control', 'max-age=60')
253 self.send_header('Content-type', 'text/html')
254 self.end_headers()
255
maruel@google.come250a9b2009-03-10 17:39:46 +0000256 self.wfile.write('<html><head><title>%s</title></head></html>' %
257 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000258
259 return True
260
261 def CacheExpiresHandler(self):
262 """This request handler yields a page with the title set to the current
263 system time, and set the page to expire on 1 Jan 2099."""
264
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000265 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000266 return False
267
268 self.send_response(200)
269 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
270 self.send_header('Content-type', 'text/html')
271 self.end_headers()
272
maruel@google.come250a9b2009-03-10 17:39:46 +0000273 self.wfile.write('<html><head><title>%s</title></head></html>' %
274 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000275
276 return True
277
278 def CacheProxyRevalidateHandler(self):
279 """This request handler yields a page with the title set to the current
280 system time, and allows caching for 60 seconds"""
281
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000282 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000283 return False
284
285 self.send_response(200)
286 self.send_header('Content-type', 'text/html')
287 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
288 self.end_headers()
289
maruel@google.come250a9b2009-03-10 17:39:46 +0000290 self.wfile.write('<html><head><title>%s</title></head></html>' %
291 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000292
293 return True
294
295 def CachePrivateHandler(self):
296 """This request handler yields a page with the title set to the current
297 system time, and allows caching for 5 seconds."""
298
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000299 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000300 return False
301
302 self.send_response(200)
303 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000304 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000305 self.end_headers()
306
maruel@google.come250a9b2009-03-10 17:39:46 +0000307 self.wfile.write('<html><head><title>%s</title></head></html>' %
308 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000309
310 return True
311
312 def CachePublicHandler(self):
313 """This request handler yields a page with the title set to the current
314 system time, and allows caching for 5 seconds."""
315
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000316 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000317 return False
318
319 self.send_response(200)
320 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000321 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000322 self.end_headers()
323
maruel@google.come250a9b2009-03-10 17:39:46 +0000324 self.wfile.write('<html><head><title>%s</title></head></html>' %
325 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000326
327 return True
328
329 def CacheSMaxAgeHandler(self):
330 """This request handler yields a page with the title set to the current
331 system time, and does not allow for caching."""
332
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000333 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000334 return False
335
336 self.send_response(200)
337 self.send_header('Content-type', 'text/html')
338 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
339 self.end_headers()
340
maruel@google.come250a9b2009-03-10 17:39:46 +0000341 self.wfile.write('<html><head><title>%s</title></head></html>' %
342 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000343
344 return True
345
346 def CacheMustRevalidateHandler(self):
347 """This request handler yields a page with the title set to the current
348 system time, and does not allow caching."""
349
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000350 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000351 return False
352
353 self.send_response(200)
354 self.send_header('Content-type', 'text/html')
355 self.send_header('Cache-Control', 'must-revalidate')
356 self.end_headers()
357
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 self.wfile.write('<html><head><title>%s</title></head></html>' %
359 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000360
361 return True
362
363 def CacheMustRevalidateMaxAgeHandler(self):
364 """This request handler yields a page with the title set to the current
365 system time, and does not allow caching event though max-age of 60
366 seconds is specified."""
367
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000368 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000369 return False
370
371 self.send_response(200)
372 self.send_header('Content-type', 'text/html')
373 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
374 self.end_headers()
375
maruel@google.come250a9b2009-03-10 17:39:46 +0000376 self.wfile.write('<html><head><title>%s</title></head></html>' %
377 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000378
379 return True
380
initial.commit94958cf2008-07-26 22:42:52 +0000381 def CacheNoStoreHandler(self):
382 """This request handler yields a page with the title set to the current
383 system time, and does not allow the page to be stored."""
384
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000385 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000386 return False
387
388 self.send_response(200)
389 self.send_header('Content-type', 'text/html')
390 self.send_header('Cache-Control', 'no-store')
391 self.end_headers()
392
maruel@google.come250a9b2009-03-10 17:39:46 +0000393 self.wfile.write('<html><head><title>%s</title></head></html>' %
394 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000395
396 return True
397
398 def CacheNoStoreMaxAgeHandler(self):
399 """This request handler yields a page with the title set to the current
400 system time, and does not allow the page to be stored even though max-age
401 of 60 seconds is specified."""
402
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000403 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
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', 'max-age=60, no-store')
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
417 def CacheNoTransformHandler(self):
418 """This request handler yields a page with the title set to the current
419 system time, and does not allow the content to transformed during
420 user-agent caching"""
421
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000422 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000423 return False
424
425 self.send_response(200)
426 self.send_header('Content-type', 'text/html')
427 self.send_header('Cache-Control', 'no-transform')
428 self.end_headers()
429
maruel@google.come250a9b2009-03-10 17:39:46 +0000430 self.wfile.write('<html><head><title>%s</title></head></html>' %
431 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000432
433 return True
434
435 def EchoHeader(self):
436 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000437 """The only difference between this function and the EchoHeaderOverride"""
438 """function is in the parameter being passed to the helper function"""
439 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000440
ananta@chromium.org219b2062009-10-23 16:09:41 +0000441 def EchoHeaderOverride(self):
442 """This handler echoes back the value of a specific request header."""
443 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
444 """IE to issue HTTP requests using the host network stack."""
445 """The Accept and Charset tests which expect the server to echo back"""
446 """the corresponding headers fail here as IE returns cached responses"""
447 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
448 """treats this request as a new request and does not cache it."""
449 return self.EchoHeaderHelper("/echoheaderoverride")
450
451 def EchoHeaderHelper(self, echo_header):
452 """This function echoes back the value of the request header passed in."""
453 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000454 return False
455
456 query_char = self.path.find('?')
457 if query_char != -1:
458 header_name = self.path[query_char+1:]
459
460 self.send_response(200)
461 self.send_header('Content-type', 'text/plain')
462 self.send_header('Cache-control', 'max-age=60000')
463 # insert a vary header to properly indicate that the cachability of this
464 # request is subject to value of the request header being echoed.
465 if len(header_name) > 0:
466 self.send_header('Vary', header_name)
467 self.end_headers()
468
469 if len(header_name) > 0:
470 self.wfile.write(self.headers.getheader(header_name))
471
472 return True
473
474 def EchoHandler(self):
475 """This handler just echoes back the payload of the request, for testing
476 form submission."""
477
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000478 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000479 return False
480
481 self.send_response(200)
482 self.send_header('Content-type', 'text/html')
483 self.end_headers()
484 length = int(self.headers.getheader('content-length'))
485 request = self.rfile.read(length)
486 self.wfile.write(request)
487 return True
488
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000489 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000490 """This is handler dumps the content of POST/PUT request to a disk file
491 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000492
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000493 prefix='/writefile/'
494 if not self.path.startswith(prefix):
495 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000496
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000497 file_name = self.path[len(prefix):]
498
499 # do not allow fancy chars in file name
500 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
501 if len(file_name) and file_name[0] != '.':
502 path = os.path.join(self.server.data_dir, 'dump', file_name);
503 length = int(self.headers.getheader('content-length'))
504 request = self.rfile.read(length)
505 f = open(path, "wb")
506 f.write(request);
507 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000508
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000509 self.send_response(200)
510 self.send_header('Content-type', 'text/html')
511 self.end_headers()
512 self.wfile.write('<html>%s</html>' % file_name)
513 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000514
initial.commit94958cf2008-07-26 22:42:52 +0000515 def EchoTitleHandler(self):
516 """This handler is like Echo, but sets the page title to the request."""
517
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000518 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000519 return False
520
521 self.send_response(200)
522 self.send_header('Content-type', 'text/html')
523 self.end_headers()
524 length = int(self.headers.getheader('content-length'))
525 request = self.rfile.read(length)
526 self.wfile.write('<html><head><title>')
527 self.wfile.write(request)
528 self.wfile.write('</title></head></html>')
529 return True
530
531 def EchoAllHandler(self):
532 """This handler yields a (more) human-readable page listing information
533 about the request header & contents."""
534
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000535 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000536 return False
537
538 self.send_response(200)
539 self.send_header('Content-type', 'text/html')
540 self.end_headers()
541 self.wfile.write('<html><head><style>'
542 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
543 '</style></head><body>'
544 '<div style="float: right">'
545 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
546 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000547
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000548 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000549 length = int(self.headers.getheader('content-length'))
550 qs = self.rfile.read(length)
551 params = cgi.parse_qs(qs, keep_blank_values=1)
552
553 for param in params:
554 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000555
556 self.wfile.write('</pre>')
557
558 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
559
560 self.wfile.write('</body></html>')
561 return True
562
563 def DownloadHandler(self):
564 """This handler sends a downloadable file with or without reporting
565 the size (6K)."""
566
567 if self.path.startswith("/download-unknown-size"):
568 send_length = False
569 elif self.path.startswith("/download-known-size"):
570 send_length = True
571 else:
572 return False
573
574 #
575 # The test which uses this functionality is attempting to send
576 # small chunks of data to the client. Use a fairly large buffer
577 # so that we'll fill chrome's IO buffer enough to force it to
578 # actually write the data.
579 # See also the comments in the client-side of this test in
580 # download_uitest.cc
581 #
582 size_chunk1 = 35*1024
583 size_chunk2 = 10*1024
584
585 self.send_response(200)
586 self.send_header('Content-type', 'application/octet-stream')
587 self.send_header('Cache-Control', 'max-age=0')
588 if send_length:
589 self.send_header('Content-Length', size_chunk1 + size_chunk2)
590 self.end_headers()
591
592 # First chunk of data:
593 self.wfile.write("*" * size_chunk1)
594 self.wfile.flush()
595
596 # handle requests until one of them clears this flag.
597 self.server.waitForDownload = True
598 while self.server.waitForDownload:
599 self.server.handle_request()
600
601 # Second chunk of data:
602 self.wfile.write("*" * size_chunk2)
603 return True
604
605 def DownloadFinishHandler(self):
606 """This handler just tells the server to finish the current download."""
607
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000608 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000609 return False
610
611 self.server.waitForDownload = False
612 self.send_response(200)
613 self.send_header('Content-type', 'text/html')
614 self.send_header('Cache-Control', 'max-age=0')
615 self.end_headers()
616 return True
617
618 def FileHandler(self):
619 """This handler sends the contents of the requested file. Wow, it's like
620 a real webserver!"""
621
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000622 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000623 if not self.path.startswith(prefix):
624 return False
625
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000626 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000627 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000628 self.rfile.read(int(self.headers.getheader('content-length')))
629
initial.commit94958cf2008-07-26 22:42:52 +0000630 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000631 if file.find('?') > -1:
632 # Ignore the query parameters entirely.
633 url, querystring = file.split('?')
634 else:
635 url = file
636 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000637 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000638 if os.path.isdir(path):
639 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000640
641 if not os.path.isfile(path):
642 print "File not found " + file + " full path:" + path
643 self.send_error(404)
644 return True
645
646 f = open(path, "rb")
647 data = f.read()
648 f.close()
649
650 # If file.mock-http-headers exists, it contains the headers we
651 # should send. Read them in and parse them.
652 headers_path = path + '.mock-http-headers'
653 if os.path.isfile(headers_path):
654 f = open(headers_path, "r")
655
656 # "HTTP/1.1 200 OK"
657 response = f.readline()
658 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
659 self.send_response(int(status_code))
660
661 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000662 header_values = re.findall('(\S+):\s*(.*)', line)
663 if len(header_values) > 0:
664 # "name: value"
665 name, value = header_values[0]
666 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000667 f.close()
668 else:
669 # Could be more generic once we support mime-type sniffing, but for
670 # now we need to set it explicitly.
671 self.send_response(200)
672 self.send_header('Content-type', self.GetMIMETypeFromName(file))
673 self.send_header('Content-Length', len(data))
674 self.end_headers()
675
676 self.wfile.write(data)
677
678 return True
679
680 def RealFileWithCommonHeaderHandler(self):
681 """This handler sends the contents of the requested file without the pseudo
682 http head!"""
683
684 prefix='/realfiles/'
685 if not self.path.startswith(prefix):
686 return False
687
688 file = self.path[len(prefix):]
689 path = os.path.join(self.server.data_dir, file)
690
691 try:
692 f = open(path, "rb")
693 data = f.read()
694 f.close()
695
696 # just simply set the MIME as octal stream
697 self.send_response(200)
698 self.send_header('Content-type', 'application/octet-stream')
699 self.end_headers()
700
701 self.wfile.write(data)
702 except:
703 self.send_error(404)
704
705 return True
706
707 def RealBZ2FileWithCommonHeaderHandler(self):
708 """This handler sends the bzip2 contents of the requested file with
709 corresponding Content-Encoding field in http head!"""
710
711 prefix='/realbz2files/'
712 if not self.path.startswith(prefix):
713 return False
714
715 parts = self.path.split('?')
716 file = parts[0][len(prefix):]
717 path = os.path.join(self.server.data_dir, file) + '.bz2'
718
719 if len(parts) > 1:
720 options = parts[1]
721 else:
722 options = ''
723
724 try:
725 self.send_response(200)
726 accept_encoding = self.headers.get("Accept-Encoding")
727 if accept_encoding.find("bzip2") != -1:
728 f = open(path, "rb")
729 data = f.read()
730 f.close()
731 self.send_header('Content-Encoding', 'bzip2')
732 self.send_header('Content-type', 'application/x-bzip2')
733 self.end_headers()
734 if options == 'incremental-header':
735 self.wfile.write(data[:1])
736 self.wfile.flush()
737 time.sleep(1.0)
738 self.wfile.write(data[1:])
739 else:
740 self.wfile.write(data)
741 else:
742 """client do not support bzip2 format, send pseudo content
743 """
744 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
745 self.end_headers()
746 self.wfile.write("you do not support bzip2 encoding")
747 except:
748 self.send_error(404)
749
750 return True
751
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000752 def SetCookieHandler(self):
753 """This handler just sets a cookie, for testing cookie handling."""
754
755 if not self._ShouldHandleRequest("/set-cookie"):
756 return False
757
758 query_char = self.path.find('?')
759 if query_char != -1:
760 cookie_values = self.path[query_char + 1:].split('&')
761 else:
762 cookie_values = ("",)
763 self.send_response(200)
764 self.send_header('Content-type', 'text/html')
765 for cookie_value in cookie_values:
766 self.send_header('Set-Cookie', '%s' % cookie_value)
767 self.end_headers()
768 for cookie_value in cookie_values:
769 self.wfile.write('%s' % cookie_value)
770 return True
771
initial.commit94958cf2008-07-26 22:42:52 +0000772 def AuthBasicHandler(self):
773 """This handler tests 'Basic' authentication. It just sends a page with
774 title 'user/pass' if you succeed."""
775
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000776 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000777 return False
778
779 username = userpass = password = b64str = ""
780
ericroman@google.com239b4d82009-03-27 04:00:22 +0000781 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
782
initial.commit94958cf2008-07-26 22:42:52 +0000783 auth = self.headers.getheader('authorization')
784 try:
785 if not auth:
786 raise Exception('no auth')
787 b64str = re.findall(r'Basic (\S+)', auth)[0]
788 userpass = base64.b64decode(b64str)
789 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
790 if password != 'secret':
791 raise Exception('wrong password')
792 except Exception, e:
793 # Authentication failed.
794 self.send_response(401)
795 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
796 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000797 if set_cookie_if_challenged:
798 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000799 self.end_headers()
800 self.wfile.write('<html><head>')
801 self.wfile.write('<title>Denied: %s</title>' % e)
802 self.wfile.write('</head><body>')
803 self.wfile.write('auth=%s<p>' % auth)
804 self.wfile.write('b64str=%s<p>' % b64str)
805 self.wfile.write('username: %s<p>' % username)
806 self.wfile.write('userpass: %s<p>' % userpass)
807 self.wfile.write('password: %s<p>' % password)
808 self.wfile.write('You sent:<br>%s<p>' % self.headers)
809 self.wfile.write('</body></html>')
810 return True
811
812 # Authentication successful. (Return a cachable response to allow for
813 # testing cached pages that require authentication.)
814 if_none_match = self.headers.getheader('if-none-match')
815 if if_none_match == "abc":
816 self.send_response(304)
817 self.end_headers()
818 else:
819 self.send_response(200)
820 self.send_header('Content-type', 'text/html')
821 self.send_header('Cache-control', 'max-age=60000')
822 self.send_header('Etag', 'abc')
823 self.end_headers()
824 self.wfile.write('<html><head>')
825 self.wfile.write('<title>%s/%s</title>' % (username, password))
826 self.wfile.write('</head><body>')
827 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000828 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000829 self.wfile.write('</body></html>')
830
831 return True
832
tonyg@chromium.org75054202010-03-31 22:06:10 +0000833 def GetNonce(self, force_reset=False):
834 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000835
tonyg@chromium.org75054202010-03-31 22:06:10 +0000836 This is a fake implementation. A real implementation would only use a given
837 nonce a single time (hence the name n-once). However, for the purposes of
838 unittesting, we don't care about the security of the nonce.
839
840 Args:
841 force_reset: Iff set, the nonce will be changed. Useful for testing the
842 "stale" response.
843 """
844 if force_reset or not self.server.nonce_time:
845 self.server.nonce_time = time.time()
846 return _new_md5('privatekey%s%d' %
847 (self.path, self.server.nonce_time)).hexdigest()
848
849 def AuthDigestHandler(self):
850 """This handler tests 'Digest' authentication.
851
852 It just sends a page with title 'user/pass' if you succeed.
853
854 A stale response is sent iff "stale" is present in the request path.
855 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000856 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000857 return False
858
tonyg@chromium.org75054202010-03-31 22:06:10 +0000859 stale = 'stale' in self.path
860 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000861 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000862 password = 'secret'
863 realm = 'testrealm'
864
865 auth = self.headers.getheader('authorization')
866 pairs = {}
867 try:
868 if not auth:
869 raise Exception('no auth')
870 if not auth.startswith('Digest'):
871 raise Exception('not digest')
872 # Pull out all the name="value" pairs as a dictionary.
873 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
874
875 # Make sure it's all valid.
876 if pairs['nonce'] != nonce:
877 raise Exception('wrong nonce')
878 if pairs['opaque'] != opaque:
879 raise Exception('wrong opaque')
880
881 # Check the 'response' value and make sure it matches our magic hash.
882 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000883 hash_a1 = _new_md5(
884 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000885 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000886 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000887 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000888 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
889 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000890 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000891
892 if pairs['response'] != response:
893 raise Exception('wrong password')
894 except Exception, e:
895 # Authentication failed.
896 self.send_response(401)
897 hdr = ('Digest '
898 'realm="%s", '
899 'domain="/", '
900 'qop="auth", '
901 'algorithm=MD5, '
902 'nonce="%s", '
903 'opaque="%s"') % (realm, nonce, opaque)
904 if stale:
905 hdr += ', stale="TRUE"'
906 self.send_header('WWW-Authenticate', hdr)
907 self.send_header('Content-type', 'text/html')
908 self.end_headers()
909 self.wfile.write('<html><head>')
910 self.wfile.write('<title>Denied: %s</title>' % e)
911 self.wfile.write('</head><body>')
912 self.wfile.write('auth=%s<p>' % auth)
913 self.wfile.write('pairs=%s<p>' % pairs)
914 self.wfile.write('You sent:<br>%s<p>' % self.headers)
915 self.wfile.write('We are replying:<br>%s<p>' % hdr)
916 self.wfile.write('</body></html>')
917 return True
918
919 # Authentication successful.
920 self.send_response(200)
921 self.send_header('Content-type', 'text/html')
922 self.end_headers()
923 self.wfile.write('<html><head>')
924 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
925 self.wfile.write('</head><body>')
926 self.wfile.write('auth=%s<p>' % auth)
927 self.wfile.write('pairs=%s<p>' % pairs)
928 self.wfile.write('</body></html>')
929
930 return True
931
932 def SlowServerHandler(self):
933 """Wait for the user suggested time before responding. The syntax is
934 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000935 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000936 return False
937 query_char = self.path.find('?')
938 wait_sec = 1.0
939 if query_char >= 0:
940 try:
941 wait_sec = int(self.path[query_char + 1:])
942 except ValueError:
943 pass
944 time.sleep(wait_sec)
945 self.send_response(200)
946 self.send_header('Content-type', 'text/plain')
947 self.end_headers()
948 self.wfile.write("waited %d seconds" % wait_sec)
949 return True
950
951 def ContentTypeHandler(self):
952 """Returns a string of html with the given content type. E.g.,
953 /contenttype?text/css returns an html file with the Content-Type
954 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000955 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000956 return False
957 query_char = self.path.find('?')
958 content_type = self.path[query_char + 1:].strip()
959 if not content_type:
960 content_type = 'text/html'
961 self.send_response(200)
962 self.send_header('Content-Type', content_type)
963 self.end_headers()
964 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
965 return True
966
967 def ServerRedirectHandler(self):
968 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000969 '/server-redirect?http://foo.bar/asdf' to redirect to
970 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000971
972 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000973 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000974 return False
975
976 query_char = self.path.find('?')
977 if query_char < 0 or len(self.path) <= query_char + 1:
978 self.sendRedirectHelp(test_name)
979 return True
980 dest = self.path[query_char + 1:]
981
982 self.send_response(301) # moved permanently
983 self.send_header('Location', dest)
984 self.send_header('Content-type', 'text/html')
985 self.end_headers()
986 self.wfile.write('<html><head>')
987 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
988
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000989 return True
initial.commit94958cf2008-07-26 22:42:52 +0000990
991 def ClientRedirectHandler(self):
992 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000993 '/client-redirect?http://foo.bar/asdf' to redirect to
994 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000995
996 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000997 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000998 return False
999
1000 query_char = self.path.find('?');
1001 if query_char < 0 or len(self.path) <= query_char + 1:
1002 self.sendRedirectHelp(test_name)
1003 return True
1004 dest = self.path[query_char + 1:]
1005
1006 self.send_response(200)
1007 self.send_header('Content-type', 'text/html')
1008 self.end_headers()
1009 self.wfile.write('<html><head>')
1010 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1011 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1012
1013 return True
1014
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001015 def ChromiumSyncTimeHandler(self):
1016 """Handle Chromium sync .../time requests.
1017
1018 The syncer sometimes checks server reachability by examining /time.
1019 """
1020 test_name = "/chromiumsync/time"
1021 if not self._ShouldHandleRequest(test_name):
1022 return False
1023
1024 self.send_response(200)
1025 self.send_header('Content-type', 'text/html')
1026 self.end_headers()
1027 return True
1028
skrul@chromium.orgfff501f2010-08-13 21:01:52 +00001029 def ChromiumSyncConfigureHandler(self):
1030 """Handle updating the configuration of the sync server.
1031
1032 The name and value pairs of the post payload will update the
1033 configuration of the sync server. Supported tuples are:
1034 user_email,<email address> - Sets the email for the fake user account
1035 """
1036 test_name = "/chromiumsync/configure"
1037 if not self._ShouldHandleRequest(test_name):
1038 return False
1039
1040 length = int(self.headers.getheader('content-length'))
1041 raw_request = self.rfile.read(length)
1042 config = cgi.parse_qs(raw_request, keep_blank_values=1)
1043
1044 success = self._sync_handler.HandleConfigure(config)
1045 if success:
1046 self.send_response(200)
1047 else:
1048 self.send_response(500)
1049 self.end_headers()
1050 return True
1051
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001052 def ChromiumSyncCommandHandler(self):
1053 """Handle a chromiumsync command arriving via http.
1054
1055 This covers all sync protocol commands: authentication, getupdates, and
1056 commit.
1057 """
1058 test_name = "/chromiumsync/command"
1059 if not self._ShouldHandleRequest(test_name):
1060 return False
1061
1062 length = int(self.headers.getheader('content-length'))
1063 raw_request = self.rfile.read(length)
1064
pathorn@chromium.org44920122010-07-27 18:25:35 +00001065 if not self.server._sync_handler:
1066 import chromiumsync
1067 self.server._sync_handler = chromiumsync.TestServer()
1068 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1069 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001070 self.send_response(http_response)
1071 self.end_headers()
1072 self.wfile.write(raw_reply)
1073 return True
1074
tony@chromium.org03266982010-03-05 03:18:42 +00001075 def MultipartHandler(self):
1076 """Send a multipart response (10 text/html pages)."""
1077 test_name = "/multipart"
1078 if not self._ShouldHandleRequest(test_name):
1079 return False
1080
1081 num_frames = 10
1082 bound = '12345'
1083 self.send_response(200)
1084 self.send_header('Content-type',
1085 'multipart/x-mixed-replace;boundary=' + bound)
1086 self.end_headers()
1087
1088 for i in xrange(num_frames):
1089 self.wfile.write('--' + bound + '\r\n')
1090 self.wfile.write('Content-type: text/html\r\n\r\n')
1091 self.wfile.write('<title>page ' + str(i) + '</title>')
1092 self.wfile.write('page ' + str(i))
1093
1094 self.wfile.write('--' + bound + '--')
1095 return True
1096
initial.commit94958cf2008-07-26 22:42:52 +00001097 def DefaultResponseHandler(self):
1098 """This is the catch-all response handler for requests that aren't handled
1099 by one of the special handlers above.
1100 Note that we specify the content-length as without it the https connection
1101 is not closed properly (and the browser keeps expecting data)."""
1102
1103 contents = "Default response given for path: " + self.path
1104 self.send_response(200)
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
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001111 def RedirectConnectHandler(self):
1112 """Sends a redirect to the CONNECT request for www.redirect.com. This
1113 response is not specified by the RFC, so the browser should not follow
1114 the redirect."""
1115
1116 if (self.path.find("www.redirect.com") < 0):
1117 return False
1118
1119 dest = "http://www.destination.com/foo.js"
1120
1121 self.send_response(302) # moved temporarily
1122 self.send_header('Location', dest)
1123 self.send_header('Connection', 'close')
1124 self.end_headers()
1125 return True
1126
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001127 def ServerAuthConnectHandler(self):
1128 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1129 response doesn't make sense because the proxy server cannot request
1130 server authentication."""
1131
1132 if (self.path.find("www.server-auth.com") < 0):
1133 return False
1134
1135 challenge = 'Basic realm="WallyWorld"'
1136
1137 self.send_response(401) # unauthorized
1138 self.send_header('WWW-Authenticate', challenge)
1139 self.send_header('Connection', 'close')
1140 self.end_headers()
1141 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001142
1143 def DefaultConnectResponseHandler(self):
1144 """This is the catch-all response handler for CONNECT requests that aren't
1145 handled by one of the special handlers above. Real Web servers respond
1146 with 400 to CONNECT requests."""
1147
1148 contents = "Your client has issued a malformed or illegal request."
1149 self.send_response(400) # bad request
1150 self.send_header('Content-type', 'text/html')
1151 self.send_header("Content-Length", len(contents))
1152 self.end_headers()
1153 self.wfile.write(contents)
1154 return True
1155
1156 def do_CONNECT(self):
1157 for handler in self._connect_handlers:
1158 if handler():
1159 return
1160
initial.commit94958cf2008-07-26 22:42:52 +00001161 def do_GET(self):
1162 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001163 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001164 return
1165
1166 def do_POST(self):
1167 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001168 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001169 return
1170
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001171 def do_PUT(self):
1172 for handler in self._put_handlers:
1173 if handler():
1174 return
1175
initial.commit94958cf2008-07-26 22:42:52 +00001176 # called by the redirect handling function when there is no parameter
1177 def sendRedirectHelp(self, redirect_name):
1178 self.send_response(200)
1179 self.send_header('Content-type', 'text/html')
1180 self.end_headers()
1181 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1182 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1183 self.wfile.write('</body></html>')
1184
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001185def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001186 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1187 requests will be stored. If the directory already exists all files and
1188 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001189 dump_dir = os.path.join(data_dir, 'dump');
1190 if os.path.isdir(dump_dir):
1191 shutil.rmtree(dump_dir)
1192 os.mkdir(dump_dir)
1193
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001194def MakeDataDir():
1195 if options.data_dir:
1196 if not os.path.isdir(options.data_dir):
1197 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1198 return None
1199 my_data_dir = options.data_dir
1200 else:
1201 # Create the default path to our data dir, relative to the exe dir.
1202 my_data_dir = os.path.dirname(sys.argv[0])
1203 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001204 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001205
1206 #TODO(ibrar): Must use Find* funtion defined in google\tools
1207 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1208
1209 return my_data_dir
1210
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001211class FileMultiplexer:
1212 def __init__(self, fd1, fd2) :
1213 self.__fd1 = fd1
1214 self.__fd2 = fd2
1215
1216 def __del__(self) :
1217 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1218 self.__fd1.close()
1219 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1220 self.__fd2.close()
1221
1222 def write(self, text) :
1223 self.__fd1.write(text)
1224 self.__fd2.write(text)
1225
1226 def flush(self) :
1227 self.__fd1.flush()
1228 self.__fd2.flush()
1229
initial.commit94958cf2008-07-26 22:42:52 +00001230def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001231 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001232 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1233 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001234
1235 port = options.port
1236
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001237 if options.server_type == SERVER_HTTP:
1238 if options.cert:
1239 # let's make sure the cert file exists.
1240 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001241 print 'specified server cert file not found: ' + options.cert + \
1242 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001243 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001244 for ca_cert in options.ssl_client_ca:
1245 if not os.path.isfile(ca_cert):
1246 print 'specified trusted client CA file not found: ' + ca_cert + \
1247 ' exiting...'
1248 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001249 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001250 options.ssl_client_auth, options.ssl_client_ca)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001251 print 'HTTPS server started on port %d...' % port
1252 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001253 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001254 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001255
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001256 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001257 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001258 server._sync_handler = None
1259
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001260 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001261
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001262 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001263 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001264 my_data_dir = MakeDataDir()
1265
1266 def line_logger(msg):
1267 if (msg.find("kill") >= 0):
1268 server.stop = True
1269 print 'shutting down server'
1270 sys.exit(0)
1271
1272 # Instantiate a dummy authorizer for managing 'virtual' users
1273 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1274
1275 # Define a new user having full r/w permissions and a read-only
1276 # anonymous user
1277 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1278
1279 authorizer.add_anonymous(my_data_dir)
1280
1281 # Instantiate FTP handler class
1282 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1283 ftp_handler.authorizer = authorizer
1284 pyftpdlib.ftpserver.logline = line_logger
1285
1286 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001287 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1288 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001289
1290 # Instantiate FTP server class and listen to 127.0.0.1:port
1291 address = ('127.0.0.1', port)
1292 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1293 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001294
1295 try:
1296 server.serve_forever()
1297 except KeyboardInterrupt:
1298 print 'shutting down server'
1299 server.stop = True
1300
1301if __name__ == '__main__':
1302 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001303 option_parser.add_option("-f", '--ftp', action='store_const',
1304 const=SERVER_FTP, default=SERVER_HTTP,
1305 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001306 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001307 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001308 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001309 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001310 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001311 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001312 help='Specify that https should be used, specify '
1313 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001314 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001315 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1316 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001317 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1318 help='Specify that the client certificate request '
1319 'should indicate that it supports the CA contained '
1320 'in the specified certificate file')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001321 option_parser.add_option('', '--file-root-url', default='/files/',
1322 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001323 option_parser.add_option('', '--never-die', default=False,
1324 action="store_true",
1325 help='Prevent the server from dying when visiting '
1326 'a /kill URL. Useful for manually running some '
1327 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001328 options, args = option_parser.parse_args()
1329
1330 sys.exit(main(options, args))