blob: 849ec1794a7cc22bd1856ba7bb7dc766aaab514b [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00002# Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
6"""This is a simple HTTP server used for testing Chrome.
7
8It supports several test URLs, as specified by the handlers in TestPageHandler.
9It defaults to living on localhost:8888.
10It can use https if you specify the flag --https=CERT where CERT is the path
11to a pem file containing the certificate and private key that should be used.
12To shut it down properly, visit localhost:8888/kill.
13"""
14
15import base64
16import BaseHTTPServer
17import cgi
initial.commit94958cf2008-07-26 22:42:52 +000018import optparse
19import os
20import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000021import shutil
initial.commit94958cf2008-07-26 22:42:52 +000022import SocketServer
23import sys
24import time
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000025import urllib2
26
27import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000028import tlslite
29import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000030
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000031try:
32 import hashlib
33 _new_md5 = hashlib.md5
34except ImportError:
35 import md5
36 _new_md5 = md5.new
37
maruel@chromium.org756cf982009-03-05 12:46:38 +000038SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000039SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000040
41debug_output = sys.stderr
42def debug(str):
43 debug_output.write(str + "\n")
44 debug_output.flush()
45
46class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
47 """This is a specialization of of BaseHTTPServer to allow it
48 to be exited cleanly (by setting its "stop" member to True)."""
49
50 def serve_forever(self):
51 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000052 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000053 while not self.stop:
54 self.handle_request()
55 self.socket.close()
56
57class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
58 """This is a specialization of StoppableHTTPerver that add https support."""
59
davidben@chromium.org31282a12010-08-07 01:10:02 +000060 def __init__(self, server_address, request_hander_class, cert_path,
61 ssl_client_auth):
initial.commit94958cf2008-07-26 22:42:52 +000062 s = open(cert_path).read()
63 x509 = tlslite.api.X509()
64 x509.parse(s)
65 self.cert_chain = tlslite.api.X509CertChain([x509])
66 s = open(cert_path).read()
67 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000068 self.ssl_client_auth = ssl_client_auth
initial.commit94958cf2008-07-26 22:42:52 +000069
70 self.session_cache = tlslite.api.SessionCache()
71 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
72
73 def handshake(self, tlsConnection):
74 """Creates the SSL connection."""
75 try:
76 tlsConnection.handshakeServer(certChain=self.cert_chain,
77 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000078 sessionCache=self.session_cache,
79 reqCert=self.ssl_client_auth)
initial.commit94958cf2008-07-26 22:42:52 +000080 tlsConnection.ignoreAbruptClose = True
81 return True
82 except tlslite.api.TLSError, error:
83 print "Handshake failure:", str(error)
84 return False
85
86class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
87
88 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000089 self._connect_handlers = [
90 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000091 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000092 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +000093 self._get_handlers = [
94 self.KillHandler,
95 self.NoCacheMaxAgeTimeHandler,
96 self.NoCacheTimeHandler,
97 self.CacheTimeHandler,
98 self.CacheExpiresHandler,
99 self.CacheProxyRevalidateHandler,
100 self.CachePrivateHandler,
101 self.CachePublicHandler,
102 self.CacheSMaxAgeHandler,
103 self.CacheMustRevalidateHandler,
104 self.CacheMustRevalidateMaxAgeHandler,
105 self.CacheNoStoreHandler,
106 self.CacheNoStoreMaxAgeHandler,
107 self.CacheNoTransformHandler,
108 self.DownloadHandler,
109 self.DownloadFinishHandler,
110 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000111 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000112 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000113 self.FileHandler,
114 self.RealFileWithCommonHeaderHandler,
115 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000116 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000117 self.AuthBasicHandler,
118 self.AuthDigestHandler,
119 self.SlowServerHandler,
120 self.ContentTypeHandler,
121 self.ServerRedirectHandler,
122 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000123 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000124 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000125 self.DefaultResponseHandler]
126 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000127 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000128 self.EchoTitleHandler,
129 self.EchoAllHandler,
skrul@chromium.orgfff501f2010-08-13 21:01:52 +0000130 self.ChromiumSyncConfigureHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000131 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000132 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000133 self._put_handlers = [
134 self.WriteFile,
135 self.EchoTitleHandler,
136 self.EchoAllHandler,
137 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000138
maruel@google.come250a9b2009-03-10 17:39:46 +0000139 self._mime_types = {
140 'gif': 'image/gif',
141 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000142 'jpg' : 'image/jpeg',
143 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000144 }
initial.commit94958cf2008-07-26 22:42:52 +0000145 self._default_mime_type = 'text/html'
146
maruel@google.come250a9b2009-03-10 17:39:46 +0000147 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
148 client_address,
149 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000150
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000151 def _ShouldHandleRequest(self, handler_name):
152 """Determines if the path can be handled by the handler.
153
154 We consider a handler valid if the path begins with the
155 handler name. It can optionally be followed by "?*", "/*".
156 """
157
158 pattern = re.compile('%s($|\?|/).*' % handler_name)
159 return pattern.match(self.path)
160
initial.commit94958cf2008-07-26 22:42:52 +0000161 def GetMIMETypeFromName(self, file_name):
162 """Returns the mime type for the specified file_name. So far it only looks
163 at the file extension."""
164
165 (shortname, extension) = os.path.splitext(file_name)
166 if len(extension) == 0:
167 # no extension.
168 return self._default_mime_type
169
ericroman@google.comc17ca532009-05-07 03:51:05 +0000170 # extension starts with a dot, so we need to remove it
171 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000172
173 def KillHandler(self):
174 """This request handler kills the server, for use when we're done"
175 with the a particular test."""
176
177 if (self.path.find("kill") < 0):
178 return False
179
180 self.send_response(200)
181 self.send_header('Content-type', 'text/html')
182 self.send_header('Cache-Control', 'max-age=0')
183 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000184 if options.never_die:
185 self.wfile.write('I cannot die!! BWAHAHA')
186 else:
187 self.wfile.write('Goodbye cruel world!')
188 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000189
190 return True
191
192 def NoCacheMaxAgeTimeHandler(self):
193 """This request handler yields a page with the title set to the current
194 system time, and no caching requested."""
195
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000196 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000197 return False
198
199 self.send_response(200)
200 self.send_header('Cache-Control', 'max-age=0')
201 self.send_header('Content-type', 'text/html')
202 self.end_headers()
203
maruel@google.come250a9b2009-03-10 17:39:46 +0000204 self.wfile.write('<html><head><title>%s</title></head></html>' %
205 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000206
207 return True
208
209 def NoCacheTimeHandler(self):
210 """This request handler yields a page with the title set to the current
211 system time, and no caching requested."""
212
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000213 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000214 return False
215
216 self.send_response(200)
217 self.send_header('Cache-Control', 'no-cache')
218 self.send_header('Content-type', 'text/html')
219 self.end_headers()
220
maruel@google.come250a9b2009-03-10 17:39:46 +0000221 self.wfile.write('<html><head><title>%s</title></head></html>' %
222 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000223
224 return True
225
226 def CacheTimeHandler(self):
227 """This request handler yields a page with the title set to the current
228 system time, and allows caching for one minute."""
229
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000230 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000231 return False
232
233 self.send_response(200)
234 self.send_header('Cache-Control', 'max-age=60')
235 self.send_header('Content-type', 'text/html')
236 self.end_headers()
237
maruel@google.come250a9b2009-03-10 17:39:46 +0000238 self.wfile.write('<html><head><title>%s</title></head></html>' %
239 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000240
241 return True
242
243 def CacheExpiresHandler(self):
244 """This request handler yields a page with the title set to the current
245 system time, and set the page to expire on 1 Jan 2099."""
246
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000247 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000248 return False
249
250 self.send_response(200)
251 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
252 self.send_header('Content-type', 'text/html')
253 self.end_headers()
254
maruel@google.come250a9b2009-03-10 17:39:46 +0000255 self.wfile.write('<html><head><title>%s</title></head></html>' %
256 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000257
258 return True
259
260 def CacheProxyRevalidateHandler(self):
261 """This request handler yields a page with the title set to the current
262 system time, and allows caching for 60 seconds"""
263
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000264 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000265 return False
266
267 self.send_response(200)
268 self.send_header('Content-type', 'text/html')
269 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
270 self.end_headers()
271
maruel@google.come250a9b2009-03-10 17:39:46 +0000272 self.wfile.write('<html><head><title>%s</title></head></html>' %
273 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000274
275 return True
276
277 def CachePrivateHandler(self):
278 """This request handler yields a page with the title set to the current
279 system time, and allows caching for 5 seconds."""
280
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000281 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000282 return False
283
284 self.send_response(200)
285 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000286 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000287 self.end_headers()
288
maruel@google.come250a9b2009-03-10 17:39:46 +0000289 self.wfile.write('<html><head><title>%s</title></head></html>' %
290 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000291
292 return True
293
294 def CachePublicHandler(self):
295 """This request handler yields a page with the title set to the current
296 system time, and allows caching for 5 seconds."""
297
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000298 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000299 return False
300
301 self.send_response(200)
302 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000303 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000304 self.end_headers()
305
maruel@google.come250a9b2009-03-10 17:39:46 +0000306 self.wfile.write('<html><head><title>%s</title></head></html>' %
307 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000308
309 return True
310
311 def CacheSMaxAgeHandler(self):
312 """This request handler yields a page with the title set to the current
313 system time, and does not allow for caching."""
314
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000315 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000316 return False
317
318 self.send_response(200)
319 self.send_header('Content-type', 'text/html')
320 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
321 self.end_headers()
322
maruel@google.come250a9b2009-03-10 17:39:46 +0000323 self.wfile.write('<html><head><title>%s</title></head></html>' %
324 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000325
326 return True
327
328 def CacheMustRevalidateHandler(self):
329 """This request handler yields a page with the title set to the current
330 system time, and does not allow caching."""
331
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000332 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000333 return False
334
335 self.send_response(200)
336 self.send_header('Content-type', 'text/html')
337 self.send_header('Cache-Control', 'must-revalidate')
338 self.end_headers()
339
maruel@google.come250a9b2009-03-10 17:39:46 +0000340 self.wfile.write('<html><head><title>%s</title></head></html>' %
341 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000342
343 return True
344
345 def CacheMustRevalidateMaxAgeHandler(self):
346 """This request handler yields a page with the title set to the current
347 system time, and does not allow caching event though max-age of 60
348 seconds is specified."""
349
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000350 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
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', 'max-age=60, 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
initial.commit94958cf2008-07-26 22:42:52 +0000363 def CacheNoStoreHandler(self):
364 """This request handler yields a page with the title set to the current
365 system time, and does not allow the page to be stored."""
366
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000367 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000368 return False
369
370 self.send_response(200)
371 self.send_header('Content-type', 'text/html')
372 self.send_header('Cache-Control', 'no-store')
373 self.end_headers()
374
maruel@google.come250a9b2009-03-10 17:39:46 +0000375 self.wfile.write('<html><head><title>%s</title></head></html>' %
376 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000377
378 return True
379
380 def CacheNoStoreMaxAgeHandler(self):
381 """This request handler yields a page with the title set to the current
382 system time, and does not allow the page to be stored even though max-age
383 of 60 seconds is specified."""
384
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000385 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
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', 'max-age=60, 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
399 def CacheNoTransformHandler(self):
400 """This request handler yields a page with the title set to the current
401 system time, and does not allow the content to transformed during
402 user-agent caching"""
403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
408 self.send_header('Content-type', 'text/html')
409 self.send_header('Cache-Control', 'no-transform')
410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417 def EchoHeader(self):
418 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000419 """The only difference between this function and the EchoHeaderOverride"""
420 """function is in the parameter being passed to the helper function"""
421 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000422
ananta@chromium.org219b2062009-10-23 16:09:41 +0000423 def EchoHeaderOverride(self):
424 """This handler echoes back the value of a specific request header."""
425 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
426 """IE to issue HTTP requests using the host network stack."""
427 """The Accept and Charset tests which expect the server to echo back"""
428 """the corresponding headers fail here as IE returns cached responses"""
429 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
430 """treats this request as a new request and does not cache it."""
431 return self.EchoHeaderHelper("/echoheaderoverride")
432
433 def EchoHeaderHelper(self, echo_header):
434 """This function echoes back the value of the request header passed in."""
435 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 query_char = self.path.find('?')
439 if query_char != -1:
440 header_name = self.path[query_char+1:]
441
442 self.send_response(200)
443 self.send_header('Content-type', 'text/plain')
444 self.send_header('Cache-control', 'max-age=60000')
445 # insert a vary header to properly indicate that the cachability of this
446 # request is subject to value of the request header being echoed.
447 if len(header_name) > 0:
448 self.send_header('Vary', header_name)
449 self.end_headers()
450
451 if len(header_name) > 0:
452 self.wfile.write(self.headers.getheader(header_name))
453
454 return True
455
456 def EchoHandler(self):
457 """This handler just echoes back the payload of the request, for testing
458 form submission."""
459
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000460 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000461 return False
462
463 self.send_response(200)
464 self.send_header('Content-type', 'text/html')
465 self.end_headers()
466 length = int(self.headers.getheader('content-length'))
467 request = self.rfile.read(length)
468 self.wfile.write(request)
469 return True
470
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000471 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000472 """This is handler dumps the content of POST/PUT request to a disk file
473 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000474
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000475 prefix='/writefile/'
476 if not self.path.startswith(prefix):
477 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000478
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000479 file_name = self.path[len(prefix):]
480
481 # do not allow fancy chars in file name
482 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
483 if len(file_name) and file_name[0] != '.':
484 path = os.path.join(self.server.data_dir, 'dump', file_name);
485 length = int(self.headers.getheader('content-length'))
486 request = self.rfile.read(length)
487 f = open(path, "wb")
488 f.write(request);
489 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000490
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000491 self.send_response(200)
492 self.send_header('Content-type', 'text/html')
493 self.end_headers()
494 self.wfile.write('<html>%s</html>' % file_name)
495 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000496
initial.commit94958cf2008-07-26 22:42:52 +0000497 def EchoTitleHandler(self):
498 """This handler is like Echo, but sets the page title to the request."""
499
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000500 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000501 return False
502
503 self.send_response(200)
504 self.send_header('Content-type', 'text/html')
505 self.end_headers()
506 length = int(self.headers.getheader('content-length'))
507 request = self.rfile.read(length)
508 self.wfile.write('<html><head><title>')
509 self.wfile.write(request)
510 self.wfile.write('</title></head></html>')
511 return True
512
513 def EchoAllHandler(self):
514 """This handler yields a (more) human-readable page listing information
515 about the request header & contents."""
516
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000517 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000518 return False
519
520 self.send_response(200)
521 self.send_header('Content-type', 'text/html')
522 self.end_headers()
523 self.wfile.write('<html><head><style>'
524 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
525 '</style></head><body>'
526 '<div style="float: right">'
527 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
528 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000529
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000530 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000531 length = int(self.headers.getheader('content-length'))
532 qs = self.rfile.read(length)
533 params = cgi.parse_qs(qs, keep_blank_values=1)
534
535 for param in params:
536 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000537
538 self.wfile.write('</pre>')
539
540 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
541
542 self.wfile.write('</body></html>')
543 return True
544
545 def DownloadHandler(self):
546 """This handler sends a downloadable file with or without reporting
547 the size (6K)."""
548
549 if self.path.startswith("/download-unknown-size"):
550 send_length = False
551 elif self.path.startswith("/download-known-size"):
552 send_length = True
553 else:
554 return False
555
556 #
557 # The test which uses this functionality is attempting to send
558 # small chunks of data to the client. Use a fairly large buffer
559 # so that we'll fill chrome's IO buffer enough to force it to
560 # actually write the data.
561 # See also the comments in the client-side of this test in
562 # download_uitest.cc
563 #
564 size_chunk1 = 35*1024
565 size_chunk2 = 10*1024
566
567 self.send_response(200)
568 self.send_header('Content-type', 'application/octet-stream')
569 self.send_header('Cache-Control', 'max-age=0')
570 if send_length:
571 self.send_header('Content-Length', size_chunk1 + size_chunk2)
572 self.end_headers()
573
574 # First chunk of data:
575 self.wfile.write("*" * size_chunk1)
576 self.wfile.flush()
577
578 # handle requests until one of them clears this flag.
579 self.server.waitForDownload = True
580 while self.server.waitForDownload:
581 self.server.handle_request()
582
583 # Second chunk of data:
584 self.wfile.write("*" * size_chunk2)
585 return True
586
587 def DownloadFinishHandler(self):
588 """This handler just tells the server to finish the current download."""
589
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000590 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000591 return False
592
593 self.server.waitForDownload = False
594 self.send_response(200)
595 self.send_header('Content-type', 'text/html')
596 self.send_header('Cache-Control', 'max-age=0')
597 self.end_headers()
598 return True
599
600 def FileHandler(self):
601 """This handler sends the contents of the requested file. Wow, it's like
602 a real webserver!"""
603
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000604 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000605 if not self.path.startswith(prefix):
606 return False
607
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000608 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000609 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000610 self.rfile.read(int(self.headers.getheader('content-length')))
611
initial.commit94958cf2008-07-26 22:42:52 +0000612 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000613 if file.find('?') > -1:
614 # Ignore the query parameters entirely.
615 url, querystring = file.split('?')
616 else:
617 url = file
618 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000619 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000620 if os.path.isdir(path):
621 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000622
623 if not os.path.isfile(path):
624 print "File not found " + file + " full path:" + path
625 self.send_error(404)
626 return True
627
628 f = open(path, "rb")
629 data = f.read()
630 f.close()
631
632 # If file.mock-http-headers exists, it contains the headers we
633 # should send. Read them in and parse them.
634 headers_path = path + '.mock-http-headers'
635 if os.path.isfile(headers_path):
636 f = open(headers_path, "r")
637
638 # "HTTP/1.1 200 OK"
639 response = f.readline()
640 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
641 self.send_response(int(status_code))
642
643 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000644 header_values = re.findall('(\S+):\s*(.*)', line)
645 if len(header_values) > 0:
646 # "name: value"
647 name, value = header_values[0]
648 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000649 f.close()
650 else:
651 # Could be more generic once we support mime-type sniffing, but for
652 # now we need to set it explicitly.
653 self.send_response(200)
654 self.send_header('Content-type', self.GetMIMETypeFromName(file))
655 self.send_header('Content-Length', len(data))
656 self.end_headers()
657
658 self.wfile.write(data)
659
660 return True
661
662 def RealFileWithCommonHeaderHandler(self):
663 """This handler sends the contents of the requested file without the pseudo
664 http head!"""
665
666 prefix='/realfiles/'
667 if not self.path.startswith(prefix):
668 return False
669
670 file = self.path[len(prefix):]
671 path = os.path.join(self.server.data_dir, file)
672
673 try:
674 f = open(path, "rb")
675 data = f.read()
676 f.close()
677
678 # just simply set the MIME as octal stream
679 self.send_response(200)
680 self.send_header('Content-type', 'application/octet-stream')
681 self.end_headers()
682
683 self.wfile.write(data)
684 except:
685 self.send_error(404)
686
687 return True
688
689 def RealBZ2FileWithCommonHeaderHandler(self):
690 """This handler sends the bzip2 contents of the requested file with
691 corresponding Content-Encoding field in http head!"""
692
693 prefix='/realbz2files/'
694 if not self.path.startswith(prefix):
695 return False
696
697 parts = self.path.split('?')
698 file = parts[0][len(prefix):]
699 path = os.path.join(self.server.data_dir, file) + '.bz2'
700
701 if len(parts) > 1:
702 options = parts[1]
703 else:
704 options = ''
705
706 try:
707 self.send_response(200)
708 accept_encoding = self.headers.get("Accept-Encoding")
709 if accept_encoding.find("bzip2") != -1:
710 f = open(path, "rb")
711 data = f.read()
712 f.close()
713 self.send_header('Content-Encoding', 'bzip2')
714 self.send_header('Content-type', 'application/x-bzip2')
715 self.end_headers()
716 if options == 'incremental-header':
717 self.wfile.write(data[:1])
718 self.wfile.flush()
719 time.sleep(1.0)
720 self.wfile.write(data[1:])
721 else:
722 self.wfile.write(data)
723 else:
724 """client do not support bzip2 format, send pseudo content
725 """
726 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
727 self.end_headers()
728 self.wfile.write("you do not support bzip2 encoding")
729 except:
730 self.send_error(404)
731
732 return True
733
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000734 def SetCookieHandler(self):
735 """This handler just sets a cookie, for testing cookie handling."""
736
737 if not self._ShouldHandleRequest("/set-cookie"):
738 return False
739
740 query_char = self.path.find('?')
741 if query_char != -1:
742 cookie_values = self.path[query_char + 1:].split('&')
743 else:
744 cookie_values = ("",)
745 self.send_response(200)
746 self.send_header('Content-type', 'text/html')
747 for cookie_value in cookie_values:
748 self.send_header('Set-Cookie', '%s' % cookie_value)
749 self.end_headers()
750 for cookie_value in cookie_values:
751 self.wfile.write('%s' % cookie_value)
752 return True
753
initial.commit94958cf2008-07-26 22:42:52 +0000754 def AuthBasicHandler(self):
755 """This handler tests 'Basic' authentication. It just sends a page with
756 title 'user/pass' if you succeed."""
757
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000758 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000759 return False
760
761 username = userpass = password = b64str = ""
762
ericroman@google.com239b4d82009-03-27 04:00:22 +0000763 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
764
initial.commit94958cf2008-07-26 22:42:52 +0000765 auth = self.headers.getheader('authorization')
766 try:
767 if not auth:
768 raise Exception('no auth')
769 b64str = re.findall(r'Basic (\S+)', auth)[0]
770 userpass = base64.b64decode(b64str)
771 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
772 if password != 'secret':
773 raise Exception('wrong password')
774 except Exception, e:
775 # Authentication failed.
776 self.send_response(401)
777 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
778 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000779 if set_cookie_if_challenged:
780 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000781 self.end_headers()
782 self.wfile.write('<html><head>')
783 self.wfile.write('<title>Denied: %s</title>' % e)
784 self.wfile.write('</head><body>')
785 self.wfile.write('auth=%s<p>' % auth)
786 self.wfile.write('b64str=%s<p>' % b64str)
787 self.wfile.write('username: %s<p>' % username)
788 self.wfile.write('userpass: %s<p>' % userpass)
789 self.wfile.write('password: %s<p>' % password)
790 self.wfile.write('You sent:<br>%s<p>' % self.headers)
791 self.wfile.write('</body></html>')
792 return True
793
794 # Authentication successful. (Return a cachable response to allow for
795 # testing cached pages that require authentication.)
796 if_none_match = self.headers.getheader('if-none-match')
797 if if_none_match == "abc":
798 self.send_response(304)
799 self.end_headers()
800 else:
801 self.send_response(200)
802 self.send_header('Content-type', 'text/html')
803 self.send_header('Cache-control', 'max-age=60000')
804 self.send_header('Etag', 'abc')
805 self.end_headers()
806 self.wfile.write('<html><head>')
807 self.wfile.write('<title>%s/%s</title>' % (username, password))
808 self.wfile.write('</head><body>')
809 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000810 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000811 self.wfile.write('</body></html>')
812
813 return True
814
tonyg@chromium.org75054202010-03-31 22:06:10 +0000815 def GetNonce(self, force_reset=False):
816 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000817
tonyg@chromium.org75054202010-03-31 22:06:10 +0000818 This is a fake implementation. A real implementation would only use a given
819 nonce a single time (hence the name n-once). However, for the purposes of
820 unittesting, we don't care about the security of the nonce.
821
822 Args:
823 force_reset: Iff set, the nonce will be changed. Useful for testing the
824 "stale" response.
825 """
826 if force_reset or not self.server.nonce_time:
827 self.server.nonce_time = time.time()
828 return _new_md5('privatekey%s%d' %
829 (self.path, self.server.nonce_time)).hexdigest()
830
831 def AuthDigestHandler(self):
832 """This handler tests 'Digest' authentication.
833
834 It just sends a page with title 'user/pass' if you succeed.
835
836 A stale response is sent iff "stale" is present in the request path.
837 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000838 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000839 return False
840
tonyg@chromium.org75054202010-03-31 22:06:10 +0000841 stale = 'stale' in self.path
842 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000843 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000844 password = 'secret'
845 realm = 'testrealm'
846
847 auth = self.headers.getheader('authorization')
848 pairs = {}
849 try:
850 if not auth:
851 raise Exception('no auth')
852 if not auth.startswith('Digest'):
853 raise Exception('not digest')
854 # Pull out all the name="value" pairs as a dictionary.
855 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
856
857 # Make sure it's all valid.
858 if pairs['nonce'] != nonce:
859 raise Exception('wrong nonce')
860 if pairs['opaque'] != opaque:
861 raise Exception('wrong opaque')
862
863 # Check the 'response' value and make sure it matches our magic hash.
864 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000865 hash_a1 = _new_md5(
866 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000867 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000868 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000869 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000870 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
871 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000872 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000873
874 if pairs['response'] != response:
875 raise Exception('wrong password')
876 except Exception, e:
877 # Authentication failed.
878 self.send_response(401)
879 hdr = ('Digest '
880 'realm="%s", '
881 'domain="/", '
882 'qop="auth", '
883 'algorithm=MD5, '
884 'nonce="%s", '
885 'opaque="%s"') % (realm, nonce, opaque)
886 if stale:
887 hdr += ', stale="TRUE"'
888 self.send_header('WWW-Authenticate', hdr)
889 self.send_header('Content-type', 'text/html')
890 self.end_headers()
891 self.wfile.write('<html><head>')
892 self.wfile.write('<title>Denied: %s</title>' % e)
893 self.wfile.write('</head><body>')
894 self.wfile.write('auth=%s<p>' % auth)
895 self.wfile.write('pairs=%s<p>' % pairs)
896 self.wfile.write('You sent:<br>%s<p>' % self.headers)
897 self.wfile.write('We are replying:<br>%s<p>' % hdr)
898 self.wfile.write('</body></html>')
899 return True
900
901 # Authentication successful.
902 self.send_response(200)
903 self.send_header('Content-type', 'text/html')
904 self.end_headers()
905 self.wfile.write('<html><head>')
906 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
907 self.wfile.write('</head><body>')
908 self.wfile.write('auth=%s<p>' % auth)
909 self.wfile.write('pairs=%s<p>' % pairs)
910 self.wfile.write('</body></html>')
911
912 return True
913
914 def SlowServerHandler(self):
915 """Wait for the user suggested time before responding. The syntax is
916 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000917 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000918 return False
919 query_char = self.path.find('?')
920 wait_sec = 1.0
921 if query_char >= 0:
922 try:
923 wait_sec = int(self.path[query_char + 1:])
924 except ValueError:
925 pass
926 time.sleep(wait_sec)
927 self.send_response(200)
928 self.send_header('Content-type', 'text/plain')
929 self.end_headers()
930 self.wfile.write("waited %d seconds" % wait_sec)
931 return True
932
933 def ContentTypeHandler(self):
934 """Returns a string of html with the given content type. E.g.,
935 /contenttype?text/css returns an html file with the Content-Type
936 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000937 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000938 return False
939 query_char = self.path.find('?')
940 content_type = self.path[query_char + 1:].strip()
941 if not content_type:
942 content_type = 'text/html'
943 self.send_response(200)
944 self.send_header('Content-Type', content_type)
945 self.end_headers()
946 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
947 return True
948
949 def ServerRedirectHandler(self):
950 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000951 '/server-redirect?http://foo.bar/asdf' to redirect to
952 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000953
954 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000955 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000956 return False
957
958 query_char = self.path.find('?')
959 if query_char < 0 or len(self.path) <= query_char + 1:
960 self.sendRedirectHelp(test_name)
961 return True
962 dest = self.path[query_char + 1:]
963
964 self.send_response(301) # moved permanently
965 self.send_header('Location', dest)
966 self.send_header('Content-type', 'text/html')
967 self.end_headers()
968 self.wfile.write('<html><head>')
969 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
970
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000971 return True
initial.commit94958cf2008-07-26 22:42:52 +0000972
973 def ClientRedirectHandler(self):
974 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000975 '/client-redirect?http://foo.bar/asdf' to redirect to
976 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000977
978 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000979 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000980 return False
981
982 query_char = self.path.find('?');
983 if query_char < 0 or len(self.path) <= query_char + 1:
984 self.sendRedirectHelp(test_name)
985 return True
986 dest = self.path[query_char + 1:]
987
988 self.send_response(200)
989 self.send_header('Content-type', 'text/html')
990 self.end_headers()
991 self.wfile.write('<html><head>')
992 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
993 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
994
995 return True
996
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000997 def ChromiumSyncTimeHandler(self):
998 """Handle Chromium sync .../time requests.
999
1000 The syncer sometimes checks server reachability by examining /time.
1001 """
1002 test_name = "/chromiumsync/time"
1003 if not self._ShouldHandleRequest(test_name):
1004 return False
1005
1006 self.send_response(200)
1007 self.send_header('Content-type', 'text/html')
1008 self.end_headers()
1009 return True
1010
skrul@chromium.orgfff501f2010-08-13 21:01:52 +00001011 def ChromiumSyncConfigureHandler(self):
1012 """Handle updating the configuration of the sync server.
1013
1014 The name and value pairs of the post payload will update the
1015 configuration of the sync server. Supported tuples are:
1016 user_email,<email address> - Sets the email for the fake user account
1017 """
1018 test_name = "/chromiumsync/configure"
1019 if not self._ShouldHandleRequest(test_name):
1020 return False
1021
1022 length = int(self.headers.getheader('content-length'))
1023 raw_request = self.rfile.read(length)
1024 config = cgi.parse_qs(raw_request, keep_blank_values=1)
1025
1026 success = self._sync_handler.HandleConfigure(config)
1027 if success:
1028 self.send_response(200)
1029 else:
1030 self.send_response(500)
1031 self.end_headers()
1032 return True
1033
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001034 def ChromiumSyncCommandHandler(self):
1035 """Handle a chromiumsync command arriving via http.
1036
1037 This covers all sync protocol commands: authentication, getupdates, and
1038 commit.
1039 """
1040 test_name = "/chromiumsync/command"
1041 if not self._ShouldHandleRequest(test_name):
1042 return False
1043
1044 length = int(self.headers.getheader('content-length'))
1045 raw_request = self.rfile.read(length)
1046
pathorn@chromium.org44920122010-07-27 18:25:35 +00001047 if not self.server._sync_handler:
1048 import chromiumsync
1049 self.server._sync_handler = chromiumsync.TestServer()
1050 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1051 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001052 self.send_response(http_response)
1053 self.end_headers()
1054 self.wfile.write(raw_reply)
1055 return True
1056
tony@chromium.org03266982010-03-05 03:18:42 +00001057 def MultipartHandler(self):
1058 """Send a multipart response (10 text/html pages)."""
1059 test_name = "/multipart"
1060 if not self._ShouldHandleRequest(test_name):
1061 return False
1062
1063 num_frames = 10
1064 bound = '12345'
1065 self.send_response(200)
1066 self.send_header('Content-type',
1067 'multipart/x-mixed-replace;boundary=' + bound)
1068 self.end_headers()
1069
1070 for i in xrange(num_frames):
1071 self.wfile.write('--' + bound + '\r\n')
1072 self.wfile.write('Content-type: text/html\r\n\r\n')
1073 self.wfile.write('<title>page ' + str(i) + '</title>')
1074 self.wfile.write('page ' + str(i))
1075
1076 self.wfile.write('--' + bound + '--')
1077 return True
1078
initial.commit94958cf2008-07-26 22:42:52 +00001079 def DefaultResponseHandler(self):
1080 """This is the catch-all response handler for requests that aren't handled
1081 by one of the special handlers above.
1082 Note that we specify the content-length as without it the https connection
1083 is not closed properly (and the browser keeps expecting data)."""
1084
1085 contents = "Default response given for path: " + self.path
1086 self.send_response(200)
1087 self.send_header('Content-type', 'text/html')
1088 self.send_header("Content-Length", len(contents))
1089 self.end_headers()
1090 self.wfile.write(contents)
1091 return True
1092
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001093 def RedirectConnectHandler(self):
1094 """Sends a redirect to the CONNECT request for www.redirect.com. This
1095 response is not specified by the RFC, so the browser should not follow
1096 the redirect."""
1097
1098 if (self.path.find("www.redirect.com") < 0):
1099 return False
1100
1101 dest = "http://www.destination.com/foo.js"
1102
1103 self.send_response(302) # moved temporarily
1104 self.send_header('Location', dest)
1105 self.send_header('Connection', 'close')
1106 self.end_headers()
1107 return True
1108
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001109 def ServerAuthConnectHandler(self):
1110 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1111 response doesn't make sense because the proxy server cannot request
1112 server authentication."""
1113
1114 if (self.path.find("www.server-auth.com") < 0):
1115 return False
1116
1117 challenge = 'Basic realm="WallyWorld"'
1118
1119 self.send_response(401) # unauthorized
1120 self.send_header('WWW-Authenticate', challenge)
1121 self.send_header('Connection', 'close')
1122 self.end_headers()
1123 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001124
1125 def DefaultConnectResponseHandler(self):
1126 """This is the catch-all response handler for CONNECT requests that aren't
1127 handled by one of the special handlers above. Real Web servers respond
1128 with 400 to CONNECT requests."""
1129
1130 contents = "Your client has issued a malformed or illegal request."
1131 self.send_response(400) # bad request
1132 self.send_header('Content-type', 'text/html')
1133 self.send_header("Content-Length", len(contents))
1134 self.end_headers()
1135 self.wfile.write(contents)
1136 return True
1137
1138 def do_CONNECT(self):
1139 for handler in self._connect_handlers:
1140 if handler():
1141 return
1142
initial.commit94958cf2008-07-26 22:42:52 +00001143 def do_GET(self):
1144 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001145 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001146 return
1147
1148 def do_POST(self):
1149 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001150 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001151 return
1152
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001153 def do_PUT(self):
1154 for handler in self._put_handlers:
1155 if handler():
1156 return
1157
initial.commit94958cf2008-07-26 22:42:52 +00001158 # called by the redirect handling function when there is no parameter
1159 def sendRedirectHelp(self, redirect_name):
1160 self.send_response(200)
1161 self.send_header('Content-type', 'text/html')
1162 self.end_headers()
1163 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1164 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1165 self.wfile.write('</body></html>')
1166
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001167def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001168 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1169 requests will be stored. If the directory already exists all files and
1170 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001171 dump_dir = os.path.join(data_dir, 'dump');
1172 if os.path.isdir(dump_dir):
1173 shutil.rmtree(dump_dir)
1174 os.mkdir(dump_dir)
1175
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001176def MakeDataDir():
1177 if options.data_dir:
1178 if not os.path.isdir(options.data_dir):
1179 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1180 return None
1181 my_data_dir = options.data_dir
1182 else:
1183 # Create the default path to our data dir, relative to the exe dir.
1184 my_data_dir = os.path.dirname(sys.argv[0])
1185 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001186 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001187
1188 #TODO(ibrar): Must use Find* funtion defined in google\tools
1189 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1190
1191 return my_data_dir
1192
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001193def TryKillingOldServer(port):
1194 # Note that an HTTP /kill request to the FTP server has the effect of
1195 # killing it.
1196 for protocol in ["http", "https"]:
1197 try:
1198 urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
1199 print "Killed old server instance on port %d (via %s)" % (port, protocol)
1200 except urllib2.URLError:
1201 # Common case, indicates no server running.
1202 pass
1203
initial.commit94958cf2008-07-26 22:42:52 +00001204def main(options, args):
1205 # redirect output to a log file so it doesn't spam the unit test output
1206 logfile = open('testserver.log', 'w')
1207 sys.stderr = sys.stdout = logfile
1208
1209 port = options.port
1210
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001211 # Try to free up the port if there's an orphaned old instance.
1212 TryKillingOldServer(port)
1213
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001214 if options.server_type == SERVER_HTTP:
1215 if options.cert:
1216 # let's make sure the cert file exists.
1217 if not os.path.isfile(options.cert):
1218 print 'specified cert file not found: ' + options.cert + ' exiting...'
1219 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001220 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
1221 options.ssl_client_auth)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001222 print 'HTTPS server started on port %d...' % port
1223 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001224 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001225 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001226
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001227 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001228 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001229 server._sync_handler = None
1230
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001231 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001232
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001233 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001234 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001235 my_data_dir = MakeDataDir()
1236
1237 def line_logger(msg):
1238 if (msg.find("kill") >= 0):
1239 server.stop = True
1240 print 'shutting down server'
1241 sys.exit(0)
1242
1243 # Instantiate a dummy authorizer for managing 'virtual' users
1244 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1245
1246 # Define a new user having full r/w permissions and a read-only
1247 # anonymous user
1248 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1249
1250 authorizer.add_anonymous(my_data_dir)
1251
1252 # Instantiate FTP handler class
1253 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1254 ftp_handler.authorizer = authorizer
1255 pyftpdlib.ftpserver.logline = line_logger
1256
1257 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001258 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1259 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001260
1261 # Instantiate FTP server class and listen to 127.0.0.1:port
1262 address = ('127.0.0.1', port)
1263 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1264 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001265
1266 try:
1267 server.serve_forever()
1268 except KeyboardInterrupt:
1269 print 'shutting down server'
1270 server.stop = True
1271
1272if __name__ == '__main__':
1273 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001274 option_parser.add_option("-f", '--ftp', action='store_const',
1275 const=SERVER_FTP, default=SERVER_HTTP,
1276 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001277 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001278 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001279 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001280 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001281 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001282 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001283 help='Specify that https should be used, specify '
1284 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001285 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001286 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1287 help='Require SSL client auth on every connection.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001288 option_parser.add_option('', '--file-root-url', default='/files/',
1289 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001290 option_parser.add_option('', '--never-die', default=False,
1291 action="store_true",
1292 help='Prevent the server from dying when visiting '
1293 'a /kill URL. Useful for manually running some '
1294 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001295 options, args = option_parser.parse_args()
1296
1297 sys.exit(main(options, args))