blob: 6c9ef8325a3c3629acad9d7caa210fdff42a2ede [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,
65 ssl_client_auth):
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
initial.commit94958cf2008-07-26 22:42:52 +000073
74 self.session_cache = tlslite.api.SessionCache()
75 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
76
77 def handshake(self, tlsConnection):
78 """Creates the SSL connection."""
79 try:
80 tlsConnection.handshakeServer(certChain=self.cert_chain,
81 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000082 sessionCache=self.session_cache,
83 reqCert=self.ssl_client_auth)
initial.commit94958cf2008-07-26 22:42:52 +000084 tlsConnection.ignoreAbruptClose = True
85 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000086 except tlslite.api.TLSAbruptCloseError:
87 # Ignore abrupt close.
88 return True
initial.commit94958cf2008-07-26 22:42:52 +000089 except tlslite.api.TLSError, error:
90 print "Handshake failure:", str(error)
91 return False
92
93class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
94
95 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000096 self._connect_handlers = [
97 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000098 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000099 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000100 self._get_handlers = [
101 self.KillHandler,
102 self.NoCacheMaxAgeTimeHandler,
103 self.NoCacheTimeHandler,
104 self.CacheTimeHandler,
105 self.CacheExpiresHandler,
106 self.CacheProxyRevalidateHandler,
107 self.CachePrivateHandler,
108 self.CachePublicHandler,
109 self.CacheSMaxAgeHandler,
110 self.CacheMustRevalidateHandler,
111 self.CacheMustRevalidateMaxAgeHandler,
112 self.CacheNoStoreHandler,
113 self.CacheNoStoreMaxAgeHandler,
114 self.CacheNoTransformHandler,
115 self.DownloadHandler,
116 self.DownloadFinishHandler,
117 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000118 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000119 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000120 self.FileHandler,
121 self.RealFileWithCommonHeaderHandler,
122 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000123 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000124 self.AuthBasicHandler,
125 self.AuthDigestHandler,
126 self.SlowServerHandler,
127 self.ContentTypeHandler,
128 self.ServerRedirectHandler,
129 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000130 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000131 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000132 self.DefaultResponseHandler]
133 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000134 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000135 self.EchoTitleHandler,
136 self.EchoAllHandler,
skrul@chromium.orgfff501f2010-08-13 21:01:52 +0000137 self.ChromiumSyncConfigureHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000138 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000139 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000140 self._put_handlers = [
141 self.WriteFile,
142 self.EchoTitleHandler,
143 self.EchoAllHandler,
144 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000145
maruel@google.come250a9b2009-03-10 17:39:46 +0000146 self._mime_types = {
147 'gif': 'image/gif',
148 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000149 'jpg' : 'image/jpeg',
150 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000151 }
initial.commit94958cf2008-07-26 22:42:52 +0000152 self._default_mime_type = 'text/html'
153
maruel@google.come250a9b2009-03-10 17:39:46 +0000154 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
155 client_address,
156 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000157
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000158 def _ShouldHandleRequest(self, handler_name):
159 """Determines if the path can be handled by the handler.
160
161 We consider a handler valid if the path begins with the
162 handler name. It can optionally be followed by "?*", "/*".
163 """
164
165 pattern = re.compile('%s($|\?|/).*' % handler_name)
166 return pattern.match(self.path)
167
initial.commit94958cf2008-07-26 22:42:52 +0000168 def GetMIMETypeFromName(self, file_name):
169 """Returns the mime type for the specified file_name. So far it only looks
170 at the file extension."""
171
172 (shortname, extension) = os.path.splitext(file_name)
173 if len(extension) == 0:
174 # no extension.
175 return self._default_mime_type
176
ericroman@google.comc17ca532009-05-07 03:51:05 +0000177 # extension starts with a dot, so we need to remove it
178 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000179
180 def KillHandler(self):
181 """This request handler kills the server, for use when we're done"
182 with the a particular test."""
183
184 if (self.path.find("kill") < 0):
185 return False
186
187 self.send_response(200)
188 self.send_header('Content-type', 'text/html')
189 self.send_header('Cache-Control', 'max-age=0')
190 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000191 if options.never_die:
192 self.wfile.write('I cannot die!! BWAHAHA')
193 else:
194 self.wfile.write('Goodbye cruel world!')
195 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000196
197 return True
198
199 def NoCacheMaxAgeTimeHandler(self):
200 """This request handler yields a page with the title set to the current
201 system time, and no caching requested."""
202
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000203 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000204 return False
205
206 self.send_response(200)
207 self.send_header('Cache-Control', 'max-age=0')
208 self.send_header('Content-type', 'text/html')
209 self.end_headers()
210
maruel@google.come250a9b2009-03-10 17:39:46 +0000211 self.wfile.write('<html><head><title>%s</title></head></html>' %
212 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000213
214 return True
215
216 def NoCacheTimeHandler(self):
217 """This request handler yields a page with the title set to the current
218 system time, and no caching requested."""
219
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000220 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000221 return False
222
223 self.send_response(200)
224 self.send_header('Cache-Control', 'no-cache')
225 self.send_header('Content-type', 'text/html')
226 self.end_headers()
227
maruel@google.come250a9b2009-03-10 17:39:46 +0000228 self.wfile.write('<html><head><title>%s</title></head></html>' %
229 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000230
231 return True
232
233 def CacheTimeHandler(self):
234 """This request handler yields a page with the title set to the current
235 system time, and allows caching for one minute."""
236
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000237 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000238 return False
239
240 self.send_response(200)
241 self.send_header('Cache-Control', 'max-age=60')
242 self.send_header('Content-type', 'text/html')
243 self.end_headers()
244
maruel@google.come250a9b2009-03-10 17:39:46 +0000245 self.wfile.write('<html><head><title>%s</title></head></html>' %
246 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000247
248 return True
249
250 def CacheExpiresHandler(self):
251 """This request handler yields a page with the title set to the current
252 system time, and set the page to expire on 1 Jan 2099."""
253
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000254 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000255 return False
256
257 self.send_response(200)
258 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
259 self.send_header('Content-type', 'text/html')
260 self.end_headers()
261
maruel@google.come250a9b2009-03-10 17:39:46 +0000262 self.wfile.write('<html><head><title>%s</title></head></html>' %
263 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000264
265 return True
266
267 def CacheProxyRevalidateHandler(self):
268 """This request handler yields a page with the title set to the current
269 system time, and allows caching for 60 seconds"""
270
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000271 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000272 return False
273
274 self.send_response(200)
275 self.send_header('Content-type', 'text/html')
276 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
277 self.end_headers()
278
maruel@google.come250a9b2009-03-10 17:39:46 +0000279 self.wfile.write('<html><head><title>%s</title></head></html>' %
280 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000281
282 return True
283
284 def CachePrivateHandler(self):
285 """This request handler yields a page with the title set to the current
286 system time, and allows caching for 5 seconds."""
287
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000288 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000289 return False
290
291 self.send_response(200)
292 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000293 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000294 self.end_headers()
295
maruel@google.come250a9b2009-03-10 17:39:46 +0000296 self.wfile.write('<html><head><title>%s</title></head></html>' %
297 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000298
299 return True
300
301 def CachePublicHandler(self):
302 """This request handler yields a page with the title set to the current
303 system time, and allows caching for 5 seconds."""
304
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000305 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000306 return False
307
308 self.send_response(200)
309 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000310 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000311 self.end_headers()
312
maruel@google.come250a9b2009-03-10 17:39:46 +0000313 self.wfile.write('<html><head><title>%s</title></head></html>' %
314 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000315
316 return True
317
318 def CacheSMaxAgeHandler(self):
319 """This request handler yields a page with the title set to the current
320 system time, and does not allow for caching."""
321
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000322 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000323 return False
324
325 self.send_response(200)
326 self.send_header('Content-type', 'text/html')
327 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
328 self.end_headers()
329
maruel@google.come250a9b2009-03-10 17:39:46 +0000330 self.wfile.write('<html><head><title>%s</title></head></html>' %
331 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000332
333 return True
334
335 def CacheMustRevalidateHandler(self):
336 """This request handler yields a page with the title set to the current
337 system time, and does not allow caching."""
338
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000339 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000340 return False
341
342 self.send_response(200)
343 self.send_header('Content-type', 'text/html')
344 self.send_header('Cache-Control', 'must-revalidate')
345 self.end_headers()
346
maruel@google.come250a9b2009-03-10 17:39:46 +0000347 self.wfile.write('<html><head><title>%s</title></head></html>' %
348 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000349
350 return True
351
352 def CacheMustRevalidateMaxAgeHandler(self):
353 """This request handler yields a page with the title set to the current
354 system time, and does not allow caching event though max-age of 60
355 seconds is specified."""
356
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000357 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000358 return False
359
360 self.send_response(200)
361 self.send_header('Content-type', 'text/html')
362 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
363 self.end_headers()
364
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 self.wfile.write('<html><head><title>%s</title></head></html>' %
366 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000367
368 return True
369
initial.commit94958cf2008-07-26 22:42:52 +0000370 def CacheNoStoreHandler(self):
371 """This request handler yields a page with the title set to the current
372 system time, and does not allow the page to be stored."""
373
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000374 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000375 return False
376
377 self.send_response(200)
378 self.send_header('Content-type', 'text/html')
379 self.send_header('Cache-Control', 'no-store')
380 self.end_headers()
381
maruel@google.come250a9b2009-03-10 17:39:46 +0000382 self.wfile.write('<html><head><title>%s</title></head></html>' %
383 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000384
385 return True
386
387 def CacheNoStoreMaxAgeHandler(self):
388 """This request handler yields a page with the title set to the current
389 system time, and does not allow the page to be stored even though max-age
390 of 60 seconds is specified."""
391
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000392 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000393 return False
394
395 self.send_response(200)
396 self.send_header('Content-type', 'text/html')
397 self.send_header('Cache-Control', 'max-age=60, no-store')
398 self.end_headers()
399
maruel@google.come250a9b2009-03-10 17:39:46 +0000400 self.wfile.write('<html><head><title>%s</title></head></html>' %
401 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000402
403 return True
404
405
406 def CacheNoTransformHandler(self):
407 """This request handler yields a page with the title set to the current
408 system time, and does not allow the content to transformed during
409 user-agent caching"""
410
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000411 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000412 return False
413
414 self.send_response(200)
415 self.send_header('Content-type', 'text/html')
416 self.send_header('Cache-Control', 'no-transform')
417 self.end_headers()
418
maruel@google.come250a9b2009-03-10 17:39:46 +0000419 self.wfile.write('<html><head><title>%s</title></head></html>' %
420 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000421
422 return True
423
424 def EchoHeader(self):
425 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000426 """The only difference between this function and the EchoHeaderOverride"""
427 """function is in the parameter being passed to the helper function"""
428 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000429
ananta@chromium.org219b2062009-10-23 16:09:41 +0000430 def EchoHeaderOverride(self):
431 """This handler echoes back the value of a specific request header."""
432 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
433 """IE to issue HTTP requests using the host network stack."""
434 """The Accept and Charset tests which expect the server to echo back"""
435 """the corresponding headers fail here as IE returns cached responses"""
436 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
437 """treats this request as a new request and does not cache it."""
438 return self.EchoHeaderHelper("/echoheaderoverride")
439
440 def EchoHeaderHelper(self, echo_header):
441 """This function echoes back the value of the request header passed in."""
442 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000443 return False
444
445 query_char = self.path.find('?')
446 if query_char != -1:
447 header_name = self.path[query_char+1:]
448
449 self.send_response(200)
450 self.send_header('Content-type', 'text/plain')
451 self.send_header('Cache-control', 'max-age=60000')
452 # insert a vary header to properly indicate that the cachability of this
453 # request is subject to value of the request header being echoed.
454 if len(header_name) > 0:
455 self.send_header('Vary', header_name)
456 self.end_headers()
457
458 if len(header_name) > 0:
459 self.wfile.write(self.headers.getheader(header_name))
460
461 return True
462
463 def EchoHandler(self):
464 """This handler just echoes back the payload of the request, for testing
465 form submission."""
466
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000467 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000468 return False
469
470 self.send_response(200)
471 self.send_header('Content-type', 'text/html')
472 self.end_headers()
473 length = int(self.headers.getheader('content-length'))
474 request = self.rfile.read(length)
475 self.wfile.write(request)
476 return True
477
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000478 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000479 """This is handler dumps the content of POST/PUT request to a disk file
480 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000481
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000482 prefix='/writefile/'
483 if not self.path.startswith(prefix):
484 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000485
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000486 file_name = self.path[len(prefix):]
487
488 # do not allow fancy chars in file name
489 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
490 if len(file_name) and file_name[0] != '.':
491 path = os.path.join(self.server.data_dir, 'dump', file_name);
492 length = int(self.headers.getheader('content-length'))
493 request = self.rfile.read(length)
494 f = open(path, "wb")
495 f.write(request);
496 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000497
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000498 self.send_response(200)
499 self.send_header('Content-type', 'text/html')
500 self.end_headers()
501 self.wfile.write('<html>%s</html>' % file_name)
502 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000503
initial.commit94958cf2008-07-26 22:42:52 +0000504 def EchoTitleHandler(self):
505 """This handler is like Echo, but sets the page title to the request."""
506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
511 self.send_header('Content-type', 'text/html')
512 self.end_headers()
513 length = int(self.headers.getheader('content-length'))
514 request = self.rfile.read(length)
515 self.wfile.write('<html><head><title>')
516 self.wfile.write(request)
517 self.wfile.write('</title></head></html>')
518 return True
519
520 def EchoAllHandler(self):
521 """This handler yields a (more) human-readable page listing information
522 about the request header & contents."""
523
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000524 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000525 return False
526
527 self.send_response(200)
528 self.send_header('Content-type', 'text/html')
529 self.end_headers()
530 self.wfile.write('<html><head><style>'
531 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
532 '</style></head><body>'
533 '<div style="float: right">'
534 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
535 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000536
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000537 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000538 length = int(self.headers.getheader('content-length'))
539 qs = self.rfile.read(length)
540 params = cgi.parse_qs(qs, keep_blank_values=1)
541
542 for param in params:
543 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000544
545 self.wfile.write('</pre>')
546
547 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
548
549 self.wfile.write('</body></html>')
550 return True
551
552 def DownloadHandler(self):
553 """This handler sends a downloadable file with or without reporting
554 the size (6K)."""
555
556 if self.path.startswith("/download-unknown-size"):
557 send_length = False
558 elif self.path.startswith("/download-known-size"):
559 send_length = True
560 else:
561 return False
562
563 #
564 # The test which uses this functionality is attempting to send
565 # small chunks of data to the client. Use a fairly large buffer
566 # so that we'll fill chrome's IO buffer enough to force it to
567 # actually write the data.
568 # See also the comments in the client-side of this test in
569 # download_uitest.cc
570 #
571 size_chunk1 = 35*1024
572 size_chunk2 = 10*1024
573
574 self.send_response(200)
575 self.send_header('Content-type', 'application/octet-stream')
576 self.send_header('Cache-Control', 'max-age=0')
577 if send_length:
578 self.send_header('Content-Length', size_chunk1 + size_chunk2)
579 self.end_headers()
580
581 # First chunk of data:
582 self.wfile.write("*" * size_chunk1)
583 self.wfile.flush()
584
585 # handle requests until one of them clears this flag.
586 self.server.waitForDownload = True
587 while self.server.waitForDownload:
588 self.server.handle_request()
589
590 # Second chunk of data:
591 self.wfile.write("*" * size_chunk2)
592 return True
593
594 def DownloadFinishHandler(self):
595 """This handler just tells the server to finish the current download."""
596
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000597 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000598 return False
599
600 self.server.waitForDownload = False
601 self.send_response(200)
602 self.send_header('Content-type', 'text/html')
603 self.send_header('Cache-Control', 'max-age=0')
604 self.end_headers()
605 return True
606
607 def FileHandler(self):
608 """This handler sends the contents of the requested file. Wow, it's like
609 a real webserver!"""
610
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000611 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000612 if not self.path.startswith(prefix):
613 return False
614
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000615 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000616 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000617 self.rfile.read(int(self.headers.getheader('content-length')))
618
initial.commit94958cf2008-07-26 22:42:52 +0000619 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000620 if file.find('?') > -1:
621 # Ignore the query parameters entirely.
622 url, querystring = file.split('?')
623 else:
624 url = file
625 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000626 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000627 if os.path.isdir(path):
628 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000629
630 if not os.path.isfile(path):
631 print "File not found " + file + " full path:" + path
632 self.send_error(404)
633 return True
634
635 f = open(path, "rb")
636 data = f.read()
637 f.close()
638
639 # If file.mock-http-headers exists, it contains the headers we
640 # should send. Read them in and parse them.
641 headers_path = path + '.mock-http-headers'
642 if os.path.isfile(headers_path):
643 f = open(headers_path, "r")
644
645 # "HTTP/1.1 200 OK"
646 response = f.readline()
647 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
648 self.send_response(int(status_code))
649
650 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000651 header_values = re.findall('(\S+):\s*(.*)', line)
652 if len(header_values) > 0:
653 # "name: value"
654 name, value = header_values[0]
655 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000656 f.close()
657 else:
658 # Could be more generic once we support mime-type sniffing, but for
659 # now we need to set it explicitly.
660 self.send_response(200)
661 self.send_header('Content-type', self.GetMIMETypeFromName(file))
662 self.send_header('Content-Length', len(data))
663 self.end_headers()
664
665 self.wfile.write(data)
666
667 return True
668
669 def RealFileWithCommonHeaderHandler(self):
670 """This handler sends the contents of the requested file without the pseudo
671 http head!"""
672
673 prefix='/realfiles/'
674 if not self.path.startswith(prefix):
675 return False
676
677 file = self.path[len(prefix):]
678 path = os.path.join(self.server.data_dir, file)
679
680 try:
681 f = open(path, "rb")
682 data = f.read()
683 f.close()
684
685 # just simply set the MIME as octal stream
686 self.send_response(200)
687 self.send_header('Content-type', 'application/octet-stream')
688 self.end_headers()
689
690 self.wfile.write(data)
691 except:
692 self.send_error(404)
693
694 return True
695
696 def RealBZ2FileWithCommonHeaderHandler(self):
697 """This handler sends the bzip2 contents of the requested file with
698 corresponding Content-Encoding field in http head!"""
699
700 prefix='/realbz2files/'
701 if not self.path.startswith(prefix):
702 return False
703
704 parts = self.path.split('?')
705 file = parts[0][len(prefix):]
706 path = os.path.join(self.server.data_dir, file) + '.bz2'
707
708 if len(parts) > 1:
709 options = parts[1]
710 else:
711 options = ''
712
713 try:
714 self.send_response(200)
715 accept_encoding = self.headers.get("Accept-Encoding")
716 if accept_encoding.find("bzip2") != -1:
717 f = open(path, "rb")
718 data = f.read()
719 f.close()
720 self.send_header('Content-Encoding', 'bzip2')
721 self.send_header('Content-type', 'application/x-bzip2')
722 self.end_headers()
723 if options == 'incremental-header':
724 self.wfile.write(data[:1])
725 self.wfile.flush()
726 time.sleep(1.0)
727 self.wfile.write(data[1:])
728 else:
729 self.wfile.write(data)
730 else:
731 """client do not support bzip2 format, send pseudo content
732 """
733 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
734 self.end_headers()
735 self.wfile.write("you do not support bzip2 encoding")
736 except:
737 self.send_error(404)
738
739 return True
740
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000741 def SetCookieHandler(self):
742 """This handler just sets a cookie, for testing cookie handling."""
743
744 if not self._ShouldHandleRequest("/set-cookie"):
745 return False
746
747 query_char = self.path.find('?')
748 if query_char != -1:
749 cookie_values = self.path[query_char + 1:].split('&')
750 else:
751 cookie_values = ("",)
752 self.send_response(200)
753 self.send_header('Content-type', 'text/html')
754 for cookie_value in cookie_values:
755 self.send_header('Set-Cookie', '%s' % cookie_value)
756 self.end_headers()
757 for cookie_value in cookie_values:
758 self.wfile.write('%s' % cookie_value)
759 return True
760
initial.commit94958cf2008-07-26 22:42:52 +0000761 def AuthBasicHandler(self):
762 """This handler tests 'Basic' authentication. It just sends a page with
763 title 'user/pass' if you succeed."""
764
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000765 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000766 return False
767
768 username = userpass = password = b64str = ""
769
ericroman@google.com239b4d82009-03-27 04:00:22 +0000770 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
771
initial.commit94958cf2008-07-26 22:42:52 +0000772 auth = self.headers.getheader('authorization')
773 try:
774 if not auth:
775 raise Exception('no auth')
776 b64str = re.findall(r'Basic (\S+)', auth)[0]
777 userpass = base64.b64decode(b64str)
778 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
779 if password != 'secret':
780 raise Exception('wrong password')
781 except Exception, e:
782 # Authentication failed.
783 self.send_response(401)
784 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
785 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000786 if set_cookie_if_challenged:
787 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000788 self.end_headers()
789 self.wfile.write('<html><head>')
790 self.wfile.write('<title>Denied: %s</title>' % e)
791 self.wfile.write('</head><body>')
792 self.wfile.write('auth=%s<p>' % auth)
793 self.wfile.write('b64str=%s<p>' % b64str)
794 self.wfile.write('username: %s<p>' % username)
795 self.wfile.write('userpass: %s<p>' % userpass)
796 self.wfile.write('password: %s<p>' % password)
797 self.wfile.write('You sent:<br>%s<p>' % self.headers)
798 self.wfile.write('</body></html>')
799 return True
800
801 # Authentication successful. (Return a cachable response to allow for
802 # testing cached pages that require authentication.)
803 if_none_match = self.headers.getheader('if-none-match')
804 if if_none_match == "abc":
805 self.send_response(304)
806 self.end_headers()
807 else:
808 self.send_response(200)
809 self.send_header('Content-type', 'text/html')
810 self.send_header('Cache-control', 'max-age=60000')
811 self.send_header('Etag', 'abc')
812 self.end_headers()
813 self.wfile.write('<html><head>')
814 self.wfile.write('<title>%s/%s</title>' % (username, password))
815 self.wfile.write('</head><body>')
816 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000817 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000818 self.wfile.write('</body></html>')
819
820 return True
821
tonyg@chromium.org75054202010-03-31 22:06:10 +0000822 def GetNonce(self, force_reset=False):
823 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000824
tonyg@chromium.org75054202010-03-31 22:06:10 +0000825 This is a fake implementation. A real implementation would only use a given
826 nonce a single time (hence the name n-once). However, for the purposes of
827 unittesting, we don't care about the security of the nonce.
828
829 Args:
830 force_reset: Iff set, the nonce will be changed. Useful for testing the
831 "stale" response.
832 """
833 if force_reset or not self.server.nonce_time:
834 self.server.nonce_time = time.time()
835 return _new_md5('privatekey%s%d' %
836 (self.path, self.server.nonce_time)).hexdigest()
837
838 def AuthDigestHandler(self):
839 """This handler tests 'Digest' authentication.
840
841 It just sends a page with title 'user/pass' if you succeed.
842
843 A stale response is sent iff "stale" is present in the request path.
844 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000845 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000846 return False
847
tonyg@chromium.org75054202010-03-31 22:06:10 +0000848 stale = 'stale' in self.path
849 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000850 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000851 password = 'secret'
852 realm = 'testrealm'
853
854 auth = self.headers.getheader('authorization')
855 pairs = {}
856 try:
857 if not auth:
858 raise Exception('no auth')
859 if not auth.startswith('Digest'):
860 raise Exception('not digest')
861 # Pull out all the name="value" pairs as a dictionary.
862 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
863
864 # Make sure it's all valid.
865 if pairs['nonce'] != nonce:
866 raise Exception('wrong nonce')
867 if pairs['opaque'] != opaque:
868 raise Exception('wrong opaque')
869
870 # Check the 'response' value and make sure it matches our magic hash.
871 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000872 hash_a1 = _new_md5(
873 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000874 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000875 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000876 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000877 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
878 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000879 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000880
881 if pairs['response'] != response:
882 raise Exception('wrong password')
883 except Exception, e:
884 # Authentication failed.
885 self.send_response(401)
886 hdr = ('Digest '
887 'realm="%s", '
888 'domain="/", '
889 'qop="auth", '
890 'algorithm=MD5, '
891 'nonce="%s", '
892 'opaque="%s"') % (realm, nonce, opaque)
893 if stale:
894 hdr += ', stale="TRUE"'
895 self.send_header('WWW-Authenticate', hdr)
896 self.send_header('Content-type', 'text/html')
897 self.end_headers()
898 self.wfile.write('<html><head>')
899 self.wfile.write('<title>Denied: %s</title>' % e)
900 self.wfile.write('</head><body>')
901 self.wfile.write('auth=%s<p>' % auth)
902 self.wfile.write('pairs=%s<p>' % pairs)
903 self.wfile.write('You sent:<br>%s<p>' % self.headers)
904 self.wfile.write('We are replying:<br>%s<p>' % hdr)
905 self.wfile.write('</body></html>')
906 return True
907
908 # Authentication successful.
909 self.send_response(200)
910 self.send_header('Content-type', 'text/html')
911 self.end_headers()
912 self.wfile.write('<html><head>')
913 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
914 self.wfile.write('</head><body>')
915 self.wfile.write('auth=%s<p>' % auth)
916 self.wfile.write('pairs=%s<p>' % pairs)
917 self.wfile.write('</body></html>')
918
919 return True
920
921 def SlowServerHandler(self):
922 """Wait for the user suggested time before responding. The syntax is
923 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000924 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000925 return False
926 query_char = self.path.find('?')
927 wait_sec = 1.0
928 if query_char >= 0:
929 try:
930 wait_sec = int(self.path[query_char + 1:])
931 except ValueError:
932 pass
933 time.sleep(wait_sec)
934 self.send_response(200)
935 self.send_header('Content-type', 'text/plain')
936 self.end_headers()
937 self.wfile.write("waited %d seconds" % wait_sec)
938 return True
939
940 def ContentTypeHandler(self):
941 """Returns a string of html with the given content type. E.g.,
942 /contenttype?text/css returns an html file with the Content-Type
943 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000944 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000945 return False
946 query_char = self.path.find('?')
947 content_type = self.path[query_char + 1:].strip()
948 if not content_type:
949 content_type = 'text/html'
950 self.send_response(200)
951 self.send_header('Content-Type', content_type)
952 self.end_headers()
953 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
954 return True
955
956 def ServerRedirectHandler(self):
957 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000958 '/server-redirect?http://foo.bar/asdf' to redirect to
959 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000960
961 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000962 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000963 return False
964
965 query_char = self.path.find('?')
966 if query_char < 0 or len(self.path) <= query_char + 1:
967 self.sendRedirectHelp(test_name)
968 return True
969 dest = self.path[query_char + 1:]
970
971 self.send_response(301) # moved permanently
972 self.send_header('Location', dest)
973 self.send_header('Content-type', 'text/html')
974 self.end_headers()
975 self.wfile.write('<html><head>')
976 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
977
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000978 return True
initial.commit94958cf2008-07-26 22:42:52 +0000979
980 def ClientRedirectHandler(self):
981 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000982 '/client-redirect?http://foo.bar/asdf' to redirect to
983 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000984
985 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000986 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000987 return False
988
989 query_char = self.path.find('?');
990 if query_char < 0 or len(self.path) <= query_char + 1:
991 self.sendRedirectHelp(test_name)
992 return True
993 dest = self.path[query_char + 1:]
994
995 self.send_response(200)
996 self.send_header('Content-type', 'text/html')
997 self.end_headers()
998 self.wfile.write('<html><head>')
999 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1000 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1001
1002 return True
1003
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001004 def ChromiumSyncTimeHandler(self):
1005 """Handle Chromium sync .../time requests.
1006
1007 The syncer sometimes checks server reachability by examining /time.
1008 """
1009 test_name = "/chromiumsync/time"
1010 if not self._ShouldHandleRequest(test_name):
1011 return False
1012
1013 self.send_response(200)
1014 self.send_header('Content-type', 'text/html')
1015 self.end_headers()
1016 return True
1017
skrul@chromium.orgfff501f2010-08-13 21:01:52 +00001018 def ChromiumSyncConfigureHandler(self):
1019 """Handle updating the configuration of the sync server.
1020
1021 The name and value pairs of the post payload will update the
1022 configuration of the sync server. Supported tuples are:
1023 user_email,<email address> - Sets the email for the fake user account
1024 """
1025 test_name = "/chromiumsync/configure"
1026 if not self._ShouldHandleRequest(test_name):
1027 return False
1028
1029 length = int(self.headers.getheader('content-length'))
1030 raw_request = self.rfile.read(length)
1031 config = cgi.parse_qs(raw_request, keep_blank_values=1)
1032
1033 success = self._sync_handler.HandleConfigure(config)
1034 if success:
1035 self.send_response(200)
1036 else:
1037 self.send_response(500)
1038 self.end_headers()
1039 return True
1040
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001041 def ChromiumSyncCommandHandler(self):
1042 """Handle a chromiumsync command arriving via http.
1043
1044 This covers all sync protocol commands: authentication, getupdates, and
1045 commit.
1046 """
1047 test_name = "/chromiumsync/command"
1048 if not self._ShouldHandleRequest(test_name):
1049 return False
1050
1051 length = int(self.headers.getheader('content-length'))
1052 raw_request = self.rfile.read(length)
1053
pathorn@chromium.org44920122010-07-27 18:25:35 +00001054 if not self.server._sync_handler:
1055 import chromiumsync
1056 self.server._sync_handler = chromiumsync.TestServer()
1057 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1058 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001059 self.send_response(http_response)
1060 self.end_headers()
1061 self.wfile.write(raw_reply)
1062 return True
1063
tony@chromium.org03266982010-03-05 03:18:42 +00001064 def MultipartHandler(self):
1065 """Send a multipart response (10 text/html pages)."""
1066 test_name = "/multipart"
1067 if not self._ShouldHandleRequest(test_name):
1068 return False
1069
1070 num_frames = 10
1071 bound = '12345'
1072 self.send_response(200)
1073 self.send_header('Content-type',
1074 'multipart/x-mixed-replace;boundary=' + bound)
1075 self.end_headers()
1076
1077 for i in xrange(num_frames):
1078 self.wfile.write('--' + bound + '\r\n')
1079 self.wfile.write('Content-type: text/html\r\n\r\n')
1080 self.wfile.write('<title>page ' + str(i) + '</title>')
1081 self.wfile.write('page ' + str(i))
1082
1083 self.wfile.write('--' + bound + '--')
1084 return True
1085
initial.commit94958cf2008-07-26 22:42:52 +00001086 def DefaultResponseHandler(self):
1087 """This is the catch-all response handler for requests that aren't handled
1088 by one of the special handlers above.
1089 Note that we specify the content-length as without it the https connection
1090 is not closed properly (and the browser keeps expecting data)."""
1091
1092 contents = "Default response given for path: " + self.path
1093 self.send_response(200)
1094 self.send_header('Content-type', 'text/html')
1095 self.send_header("Content-Length", len(contents))
1096 self.end_headers()
1097 self.wfile.write(contents)
1098 return True
1099
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001100 def RedirectConnectHandler(self):
1101 """Sends a redirect to the CONNECT request for www.redirect.com. This
1102 response is not specified by the RFC, so the browser should not follow
1103 the redirect."""
1104
1105 if (self.path.find("www.redirect.com") < 0):
1106 return False
1107
1108 dest = "http://www.destination.com/foo.js"
1109
1110 self.send_response(302) # moved temporarily
1111 self.send_header('Location', dest)
1112 self.send_header('Connection', 'close')
1113 self.end_headers()
1114 return True
1115
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001116 def ServerAuthConnectHandler(self):
1117 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1118 response doesn't make sense because the proxy server cannot request
1119 server authentication."""
1120
1121 if (self.path.find("www.server-auth.com") < 0):
1122 return False
1123
1124 challenge = 'Basic realm="WallyWorld"'
1125
1126 self.send_response(401) # unauthorized
1127 self.send_header('WWW-Authenticate', challenge)
1128 self.send_header('Connection', 'close')
1129 self.end_headers()
1130 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001131
1132 def DefaultConnectResponseHandler(self):
1133 """This is the catch-all response handler for CONNECT requests that aren't
1134 handled by one of the special handlers above. Real Web servers respond
1135 with 400 to CONNECT requests."""
1136
1137 contents = "Your client has issued a malformed or illegal request."
1138 self.send_response(400) # bad request
1139 self.send_header('Content-type', 'text/html')
1140 self.send_header("Content-Length", len(contents))
1141 self.end_headers()
1142 self.wfile.write(contents)
1143 return True
1144
1145 def do_CONNECT(self):
1146 for handler in self._connect_handlers:
1147 if handler():
1148 return
1149
initial.commit94958cf2008-07-26 22:42:52 +00001150 def do_GET(self):
1151 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001152 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001153 return
1154
1155 def do_POST(self):
1156 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001157 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001158 return
1159
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001160 def do_PUT(self):
1161 for handler in self._put_handlers:
1162 if handler():
1163 return
1164
initial.commit94958cf2008-07-26 22:42:52 +00001165 # called by the redirect handling function when there is no parameter
1166 def sendRedirectHelp(self, redirect_name):
1167 self.send_response(200)
1168 self.send_header('Content-type', 'text/html')
1169 self.end_headers()
1170 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1171 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1172 self.wfile.write('</body></html>')
1173
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001174def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001175 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1176 requests will be stored. If the directory already exists all files and
1177 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001178 dump_dir = os.path.join(data_dir, 'dump');
1179 if os.path.isdir(dump_dir):
1180 shutil.rmtree(dump_dir)
1181 os.mkdir(dump_dir)
1182
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001183def MakeDataDir():
1184 if options.data_dir:
1185 if not os.path.isdir(options.data_dir):
1186 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1187 return None
1188 my_data_dir = options.data_dir
1189 else:
1190 # Create the default path to our data dir, relative to the exe dir.
1191 my_data_dir = os.path.dirname(sys.argv[0])
1192 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001193 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001194
1195 #TODO(ibrar): Must use Find* funtion defined in google\tools
1196 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1197
1198 return my_data_dir
1199
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001200def TryKillingOldServer(port):
1201 # Note that an HTTP /kill request to the FTP server has the effect of
1202 # killing it.
1203 for protocol in ["http", "https"]:
1204 try:
1205 urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
1206 print "Killed old server instance on port %d (via %s)" % (port, protocol)
1207 except urllib2.URLError:
1208 # Common case, indicates no server running.
1209 pass
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
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001237 # Try to free up the port if there's an orphaned old instance.
1238 TryKillingOldServer(port)
1239
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001240 if options.server_type == SERVER_HTTP:
1241 if options.cert:
1242 # let's make sure the cert file exists.
1243 if not os.path.isfile(options.cert):
1244 print 'specified cert file not found: ' + options.cert + ' exiting...'
1245 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001246 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
1247 options.ssl_client_auth)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001248 print 'HTTPS server started on port %d...' % port
1249 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001250 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001251 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001252
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001253 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001254 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001255 server._sync_handler = None
1256
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001257 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001258
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001259 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001260 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001261 my_data_dir = MakeDataDir()
1262
1263 def line_logger(msg):
1264 if (msg.find("kill") >= 0):
1265 server.stop = True
1266 print 'shutting down server'
1267 sys.exit(0)
1268
1269 # Instantiate a dummy authorizer for managing 'virtual' users
1270 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1271
1272 # Define a new user having full r/w permissions and a read-only
1273 # anonymous user
1274 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1275
1276 authorizer.add_anonymous(my_data_dir)
1277
1278 # Instantiate FTP handler class
1279 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1280 ftp_handler.authorizer = authorizer
1281 pyftpdlib.ftpserver.logline = line_logger
1282
1283 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001284 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1285 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001286
1287 # Instantiate FTP server class and listen to 127.0.0.1:port
1288 address = ('127.0.0.1', port)
1289 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1290 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001291
1292 try:
1293 server.serve_forever()
1294 except KeyboardInterrupt:
1295 print 'shutting down server'
1296 server.stop = True
1297
1298if __name__ == '__main__':
1299 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001300 option_parser.add_option("-f", '--ftp', action='store_const',
1301 const=SERVER_FTP, default=SERVER_HTTP,
1302 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001303 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001304 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001305 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001306 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001307 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001308 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001309 help='Specify that https should be used, specify '
1310 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001311 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001312 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1313 help='Require SSL client auth on every connection.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001314 option_parser.add_option('', '--file-root-url', default='/files/',
1315 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001316 option_parser.add_option('', '--never-die', default=False,
1317 action="store_true",
1318 help='Prevent the server from dying when visiting '
1319 'a /kill URL. Useful for manually running some '
1320 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001321 options, args = option_parser.parse_args()
1322
1323 sys.exit(main(options, args))