blob: 999cde729332b5a6397f06df9e118203e2851860 [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
31import chromiumsync
erikkay@google.comd5182ff2009-01-08 20:45:27 +000032
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000033try:
34 import hashlib
35 _new_md5 = hashlib.md5
36except ImportError:
37 import md5
38 _new_md5 = md5.new
39
maruel@chromium.org756cf982009-03-05 12:46:38 +000040SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000041SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000042
43debug_output = sys.stderr
44def debug(str):
45 debug_output.write(str + "\n")
46 debug_output.flush()
47
48class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
49 """This is a specialization of of BaseHTTPServer to allow it
50 to be exited cleanly (by setting its "stop" member to True)."""
51
52 def serve_forever(self):
53 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000054 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000055 while not self.stop:
56 self.handle_request()
57 self.socket.close()
58
59class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
60 """This is a specialization of StoppableHTTPerver that add https support."""
61
62 def __init__(self, server_address, request_hander_class, cert_path):
63 s = open(cert_path).read()
64 x509 = tlslite.api.X509()
65 x509.parse(s)
66 self.cert_chain = tlslite.api.X509CertChain([x509])
67 s = open(cert_path).read()
68 self.private_key = tlslite.api.parsePEMKey(s, private=True)
69
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,
78 sessionCache=self.session_cache)
79 tlsConnection.ignoreAbruptClose = True
80 return True
81 except tlslite.api.TLSError, error:
82 print "Handshake failure:", str(error)
83 return False
84
85class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
86
87 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000088 self._connect_handlers = [
89 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000090 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000091 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +000092 self._get_handlers = [
93 self.KillHandler,
94 self.NoCacheMaxAgeTimeHandler,
95 self.NoCacheTimeHandler,
96 self.CacheTimeHandler,
97 self.CacheExpiresHandler,
98 self.CacheProxyRevalidateHandler,
99 self.CachePrivateHandler,
100 self.CachePublicHandler,
101 self.CacheSMaxAgeHandler,
102 self.CacheMustRevalidateHandler,
103 self.CacheMustRevalidateMaxAgeHandler,
104 self.CacheNoStoreHandler,
105 self.CacheNoStoreMaxAgeHandler,
106 self.CacheNoTransformHandler,
107 self.DownloadHandler,
108 self.DownloadFinishHandler,
109 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000110 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000111 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000112 self.FileHandler,
113 self.RealFileWithCommonHeaderHandler,
114 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000115 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000116 self.AuthBasicHandler,
117 self.AuthDigestHandler,
118 self.SlowServerHandler,
119 self.ContentTypeHandler,
120 self.ServerRedirectHandler,
121 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000122 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000123 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000124 self.DefaultResponseHandler]
125 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000126 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000127 self.EchoTitleHandler,
128 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000129 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000130 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000131 self._put_handlers = [
132 self.WriteFile,
133 self.EchoTitleHandler,
134 self.EchoAllHandler,
135 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000136
maruel@google.come250a9b2009-03-10 17:39:46 +0000137 self._mime_types = {
138 'gif': 'image/gif',
139 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000140 'jpg' : 'image/jpeg',
141 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000142 }
initial.commit94958cf2008-07-26 22:42:52 +0000143 self._default_mime_type = 'text/html'
144
maruel@google.come250a9b2009-03-10 17:39:46 +0000145 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
146 client_address,
147 socket_server)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000148 # Class variable; shared across requests.
149 _sync_handler = chromiumsync.TestServer()
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
1011 def ChromiumSyncCommandHandler(self):
1012 """Handle a chromiumsync command arriving via http.
1013
1014 This covers all sync protocol commands: authentication, getupdates, and
1015 commit.
1016 """
1017 test_name = "/chromiumsync/command"
1018 if not self._ShouldHandleRequest(test_name):
1019 return False
1020
1021 length = int(self.headers.getheader('content-length'))
1022 raw_request = self.rfile.read(length)
1023
1024 http_response, raw_reply = self._sync_handler.HandleCommand(raw_request)
1025 self.send_response(http_response)
1026 self.end_headers()
1027 self.wfile.write(raw_reply)
1028 return True
1029
tony@chromium.org03266982010-03-05 03:18:42 +00001030 def MultipartHandler(self):
1031 """Send a multipart response (10 text/html pages)."""
1032 test_name = "/multipart"
1033 if not self._ShouldHandleRequest(test_name):
1034 return False
1035
1036 num_frames = 10
1037 bound = '12345'
1038 self.send_response(200)
1039 self.send_header('Content-type',
1040 'multipart/x-mixed-replace;boundary=' + bound)
1041 self.end_headers()
1042
1043 for i in xrange(num_frames):
1044 self.wfile.write('--' + bound + '\r\n')
1045 self.wfile.write('Content-type: text/html\r\n\r\n')
1046 self.wfile.write('<title>page ' + str(i) + '</title>')
1047 self.wfile.write('page ' + str(i))
1048
1049 self.wfile.write('--' + bound + '--')
1050 return True
1051
initial.commit94958cf2008-07-26 22:42:52 +00001052 def DefaultResponseHandler(self):
1053 """This is the catch-all response handler for requests that aren't handled
1054 by one of the special handlers above.
1055 Note that we specify the content-length as without it the https connection
1056 is not closed properly (and the browser keeps expecting data)."""
1057
1058 contents = "Default response given for path: " + self.path
1059 self.send_response(200)
1060 self.send_header('Content-type', 'text/html')
1061 self.send_header("Content-Length", len(contents))
1062 self.end_headers()
1063 self.wfile.write(contents)
1064 return True
1065
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001066 def RedirectConnectHandler(self):
1067 """Sends a redirect to the CONNECT request for www.redirect.com. This
1068 response is not specified by the RFC, so the browser should not follow
1069 the redirect."""
1070
1071 if (self.path.find("www.redirect.com") < 0):
1072 return False
1073
1074 dest = "http://www.destination.com/foo.js"
1075
1076 self.send_response(302) # moved temporarily
1077 self.send_header('Location', dest)
1078 self.send_header('Connection', 'close')
1079 self.end_headers()
1080 return True
1081
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001082 def ServerAuthConnectHandler(self):
1083 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1084 response doesn't make sense because the proxy server cannot request
1085 server authentication."""
1086
1087 if (self.path.find("www.server-auth.com") < 0):
1088 return False
1089
1090 challenge = 'Basic realm="WallyWorld"'
1091
1092 self.send_response(401) # unauthorized
1093 self.send_header('WWW-Authenticate', challenge)
1094 self.send_header('Connection', 'close')
1095 self.end_headers()
1096 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001097
1098 def DefaultConnectResponseHandler(self):
1099 """This is the catch-all response handler for CONNECT requests that aren't
1100 handled by one of the special handlers above. Real Web servers respond
1101 with 400 to CONNECT requests."""
1102
1103 contents = "Your client has issued a malformed or illegal request."
1104 self.send_response(400) # bad request
1105 self.send_header('Content-type', 'text/html')
1106 self.send_header("Content-Length", len(contents))
1107 self.end_headers()
1108 self.wfile.write(contents)
1109 return True
1110
1111 def do_CONNECT(self):
1112 for handler in self._connect_handlers:
1113 if handler():
1114 return
1115
initial.commit94958cf2008-07-26 22:42:52 +00001116 def do_GET(self):
1117 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001118 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001119 return
1120
1121 def do_POST(self):
1122 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001123 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001124 return
1125
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001126 def do_PUT(self):
1127 for handler in self._put_handlers:
1128 if handler():
1129 return
1130
initial.commit94958cf2008-07-26 22:42:52 +00001131 # called by the redirect handling function when there is no parameter
1132 def sendRedirectHelp(self, redirect_name):
1133 self.send_response(200)
1134 self.send_header('Content-type', 'text/html')
1135 self.end_headers()
1136 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1137 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1138 self.wfile.write('</body></html>')
1139
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001140def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001141 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1142 requests will be stored. If the directory already exists all files and
1143 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001144 dump_dir = os.path.join(data_dir, 'dump');
1145 if os.path.isdir(dump_dir):
1146 shutil.rmtree(dump_dir)
1147 os.mkdir(dump_dir)
1148
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001149def MakeDataDir():
1150 if options.data_dir:
1151 if not os.path.isdir(options.data_dir):
1152 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1153 return None
1154 my_data_dir = options.data_dir
1155 else:
1156 # Create the default path to our data dir, relative to the exe dir.
1157 my_data_dir = os.path.dirname(sys.argv[0])
1158 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001159 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001160
1161 #TODO(ibrar): Must use Find* funtion defined in google\tools
1162 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1163
1164 return my_data_dir
1165
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001166def TryKillingOldServer(port):
1167 # Note that an HTTP /kill request to the FTP server has the effect of
1168 # killing it.
1169 for protocol in ["http", "https"]:
1170 try:
1171 urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
1172 print "Killed old server instance on port %d (via %s)" % (port, protocol)
1173 except urllib2.URLError:
1174 # Common case, indicates no server running.
1175 pass
1176
initial.commit94958cf2008-07-26 22:42:52 +00001177def main(options, args):
1178 # redirect output to a log file so it doesn't spam the unit test output
1179 logfile = open('testserver.log', 'w')
1180 sys.stderr = sys.stdout = logfile
1181
1182 port = options.port
1183
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001184 # Try to free up the port if there's an orphaned old instance.
1185 TryKillingOldServer(port)
1186
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001187 if options.server_type == SERVER_HTTP:
1188 if options.cert:
1189 # let's make sure the cert file exists.
1190 if not os.path.isfile(options.cert):
1191 print 'specified cert file not found: ' + options.cert + ' exiting...'
1192 return
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001193 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001194 print 'HTTPS server started on port %d...' % port
1195 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001196 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001197 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001198
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001199 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001200 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001201 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001202
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001203 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001204 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001205 my_data_dir = MakeDataDir()
1206
1207 def line_logger(msg):
1208 if (msg.find("kill") >= 0):
1209 server.stop = True
1210 print 'shutting down server'
1211 sys.exit(0)
1212
1213 # Instantiate a dummy authorizer for managing 'virtual' users
1214 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1215
1216 # Define a new user having full r/w permissions and a read-only
1217 # anonymous user
1218 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1219
1220 authorizer.add_anonymous(my_data_dir)
1221
1222 # Instantiate FTP handler class
1223 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1224 ftp_handler.authorizer = authorizer
1225 pyftpdlib.ftpserver.logline = line_logger
1226
1227 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001228 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1229 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001230
1231 # Instantiate FTP server class and listen to 127.0.0.1:port
1232 address = ('127.0.0.1', port)
1233 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1234 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001235
1236 try:
1237 server.serve_forever()
1238 except KeyboardInterrupt:
1239 print 'shutting down server'
1240 server.stop = True
1241
1242if __name__ == '__main__':
1243 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001244 option_parser.add_option("-f", '--ftp', action='store_const',
1245 const=SERVER_FTP, default=SERVER_HTTP,
1246 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001247 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001248 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001249 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001250 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001251 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001252 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001253 help='Specify that https should be used, specify '
1254 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001255 'the server should use.')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001256 option_parser.add_option('', '--file-root-url', default='/files/',
1257 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001258 option_parser.add_option('', '--never-die', default=False,
1259 action="store_true",
1260 help='Prevent the server from dying when visiting '
1261 'a /kill URL. Useful for manually running some '
1262 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001263 options, args = option_parser.parse_args()
1264
1265 sys.exit(main(options, args))