blob: e38bfc7518f9dd0d29a46321d6bea53d2575438e [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00002# Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
6"""This is a simple HTTP server used for testing Chrome.
7
8It supports several test URLs, as specified by the handlers in TestPageHandler.
9It defaults to living on localhost:8888.
10It can use https if you specify the flag --https=CERT where CERT is the path
11to a pem file containing the certificate and private key that should be used.
12To shut it down properly, visit localhost:8888/kill.
13"""
14
15import base64
16import BaseHTTPServer
17import cgi
initial.commit94958cf2008-07-26 22:42:52 +000018import optparse
19import os
20import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000021import shutil
initial.commit94958cf2008-07-26 22:42:52 +000022import SocketServer
23import sys
24import time
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000025import urllib2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000026import warnings
27
28# Ignore deprecation warnings, they make our output more cluttered.
29warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000030
31import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000032import tlslite
33import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000034
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000035try:
36 import hashlib
37 _new_md5 = hashlib.md5
38except ImportError:
39 import md5
40 _new_md5 = md5.new
41
maruel@chromium.org756cf982009-03-05 12:46:38 +000042SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000043SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000044
45debug_output = sys.stderr
46def debug(str):
47 debug_output.write(str + "\n")
48 debug_output.flush()
49
50class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
51 """This is a specialization of of BaseHTTPServer to allow it
52 to be exited cleanly (by setting its "stop" member to True)."""
53
54 def serve_forever(self):
55 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000056 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000057 while not self.stop:
58 self.handle_request()
59 self.socket.close()
60
61class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
62 """This is a specialization of StoppableHTTPerver that add https support."""
63
davidben@chromium.org31282a12010-08-07 01:10:02 +000064 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000065 ssl_client_auth, ssl_client_cas):
initial.commit94958cf2008-07-26 22:42:52 +000066 s = open(cert_path).read()
67 x509 = tlslite.api.X509()
68 x509.parse(s)
69 self.cert_chain = tlslite.api.X509CertChain([x509])
70 s = open(cert_path).read()
71 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000072 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000073 self.ssl_client_cas = []
74 for ca_file in ssl_client_cas:
75 s = open(ca_file).read()
76 x509 = tlslite.api.X509()
77 x509.parse(s)
78 self.ssl_client_cas.append(x509.subject)
initial.commit94958cf2008-07-26 22:42:52 +000079
80 self.session_cache = tlslite.api.SessionCache()
81 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
82
83 def handshake(self, tlsConnection):
84 """Creates the SSL connection."""
85 try:
86 tlsConnection.handshakeServer(certChain=self.cert_chain,
87 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000088 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000089 reqCert=self.ssl_client_auth,
90 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +000091 tlsConnection.ignoreAbruptClose = True
92 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000093 except tlslite.api.TLSAbruptCloseError:
94 # Ignore abrupt close.
95 return True
initial.commit94958cf2008-07-26 22:42:52 +000096 except tlslite.api.TLSError, error:
97 print "Handshake failure:", str(error)
98 return False
99
100class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
101
102 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000103 self._connect_handlers = [
104 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000105 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000106 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000107 self._get_handlers = [
108 self.KillHandler,
109 self.NoCacheMaxAgeTimeHandler,
110 self.NoCacheTimeHandler,
111 self.CacheTimeHandler,
112 self.CacheExpiresHandler,
113 self.CacheProxyRevalidateHandler,
114 self.CachePrivateHandler,
115 self.CachePublicHandler,
116 self.CacheSMaxAgeHandler,
117 self.CacheMustRevalidateHandler,
118 self.CacheMustRevalidateMaxAgeHandler,
119 self.CacheNoStoreHandler,
120 self.CacheNoStoreMaxAgeHandler,
121 self.CacheNoTransformHandler,
122 self.DownloadHandler,
123 self.DownloadFinishHandler,
124 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000125 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000126 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000127 self.FileHandler,
128 self.RealFileWithCommonHeaderHandler,
129 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000130 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000131 self.AuthBasicHandler,
132 self.AuthDigestHandler,
133 self.SlowServerHandler,
134 self.ContentTypeHandler,
135 self.ServerRedirectHandler,
136 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000137 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000138 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000139 self.DefaultResponseHandler]
140 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000141 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000142 self.EchoTitleHandler,
143 self.EchoAllHandler,
skrul@chromium.orgfff501f2010-08-13 21:01:52 +0000144 self.ChromiumSyncConfigureHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000145 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000146 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000147 self._put_handlers = [
148 self.WriteFile,
149 self.EchoTitleHandler,
150 self.EchoAllHandler,
151 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000152
maruel@google.come250a9b2009-03-10 17:39:46 +0000153 self._mime_types = {
154 'gif': 'image/gif',
155 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000156 'jpg' : 'image/jpeg',
157 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000158 }
initial.commit94958cf2008-07-26 22:42:52 +0000159 self._default_mime_type = 'text/html'
160
maruel@google.come250a9b2009-03-10 17:39:46 +0000161 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
162 client_address,
163 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000164
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000165 def _ShouldHandleRequest(self, handler_name):
166 """Determines if the path can be handled by the handler.
167
168 We consider a handler valid if the path begins with the
169 handler name. It can optionally be followed by "?*", "/*".
170 """
171
172 pattern = re.compile('%s($|\?|/).*' % handler_name)
173 return pattern.match(self.path)
174
initial.commit94958cf2008-07-26 22:42:52 +0000175 def GetMIMETypeFromName(self, file_name):
176 """Returns the mime type for the specified file_name. So far it only looks
177 at the file extension."""
178
179 (shortname, extension) = os.path.splitext(file_name)
180 if len(extension) == 0:
181 # no extension.
182 return self._default_mime_type
183
ericroman@google.comc17ca532009-05-07 03:51:05 +0000184 # extension starts with a dot, so we need to remove it
185 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000186
187 def KillHandler(self):
188 """This request handler kills the server, for use when we're done"
189 with the a particular test."""
190
191 if (self.path.find("kill") < 0):
192 return False
193
194 self.send_response(200)
195 self.send_header('Content-type', 'text/html')
196 self.send_header('Cache-Control', 'max-age=0')
197 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000198 if options.never_die:
199 self.wfile.write('I cannot die!! BWAHAHA')
200 else:
201 self.wfile.write('Goodbye cruel world!')
202 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000203
204 return True
205
206 def NoCacheMaxAgeTimeHandler(self):
207 """This request handler yields a page with the title set to the current
208 system time, and no caching requested."""
209
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000210 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000211 return False
212
213 self.send_response(200)
214 self.send_header('Cache-Control', 'max-age=0')
215 self.send_header('Content-type', 'text/html')
216 self.end_headers()
217
maruel@google.come250a9b2009-03-10 17:39:46 +0000218 self.wfile.write('<html><head><title>%s</title></head></html>' %
219 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000220
221 return True
222
223 def NoCacheTimeHandler(self):
224 """This request handler yields a page with the title set to the current
225 system time, and no caching requested."""
226
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000227 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000228 return False
229
230 self.send_response(200)
231 self.send_header('Cache-Control', 'no-cache')
232 self.send_header('Content-type', 'text/html')
233 self.end_headers()
234
maruel@google.come250a9b2009-03-10 17:39:46 +0000235 self.wfile.write('<html><head><title>%s</title></head></html>' %
236 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000237
238 return True
239
240 def CacheTimeHandler(self):
241 """This request handler yields a page with the title set to the current
242 system time, and allows caching for one minute."""
243
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000244 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000245 return False
246
247 self.send_response(200)
248 self.send_header('Cache-Control', 'max-age=60')
249 self.send_header('Content-type', 'text/html')
250 self.end_headers()
251
maruel@google.come250a9b2009-03-10 17:39:46 +0000252 self.wfile.write('<html><head><title>%s</title></head></html>' %
253 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000254
255 return True
256
257 def CacheExpiresHandler(self):
258 """This request handler yields a page with the title set to the current
259 system time, and set the page to expire on 1 Jan 2099."""
260
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000261 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000262 return False
263
264 self.send_response(200)
265 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
266 self.send_header('Content-type', 'text/html')
267 self.end_headers()
268
maruel@google.come250a9b2009-03-10 17:39:46 +0000269 self.wfile.write('<html><head><title>%s</title></head></html>' %
270 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000271
272 return True
273
274 def CacheProxyRevalidateHandler(self):
275 """This request handler yields a page with the title set to the current
276 system time, and allows caching for 60 seconds"""
277
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000278 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000279 return False
280
281 self.send_response(200)
282 self.send_header('Content-type', 'text/html')
283 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
284 self.end_headers()
285
maruel@google.come250a9b2009-03-10 17:39:46 +0000286 self.wfile.write('<html><head><title>%s</title></head></html>' %
287 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000288
289 return True
290
291 def CachePrivateHandler(self):
292 """This request handler yields a page with the title set to the current
293 system time, and allows caching for 5 seconds."""
294
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000295 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000296 return False
297
298 self.send_response(200)
299 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000300 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000301 self.end_headers()
302
maruel@google.come250a9b2009-03-10 17:39:46 +0000303 self.wfile.write('<html><head><title>%s</title></head></html>' %
304 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000305
306 return True
307
308 def CachePublicHandler(self):
309 """This request handler yields a page with the title set to the current
310 system time, and allows caching for 5 seconds."""
311
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000312 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000313 return False
314
315 self.send_response(200)
316 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000317 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000318 self.end_headers()
319
maruel@google.come250a9b2009-03-10 17:39:46 +0000320 self.wfile.write('<html><head><title>%s</title></head></html>' %
321 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000322
323 return True
324
325 def CacheSMaxAgeHandler(self):
326 """This request handler yields a page with the title set to the current
327 system time, and does not allow for caching."""
328
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000329 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000330 return False
331
332 self.send_response(200)
333 self.send_header('Content-type', 'text/html')
334 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
335 self.end_headers()
336
maruel@google.come250a9b2009-03-10 17:39:46 +0000337 self.wfile.write('<html><head><title>%s</title></head></html>' %
338 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000339
340 return True
341
342 def CacheMustRevalidateHandler(self):
343 """This request handler yields a page with the title set to the current
344 system time, and does not allow caching."""
345
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000346 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000347 return False
348
349 self.send_response(200)
350 self.send_header('Content-type', 'text/html')
351 self.send_header('Cache-Control', 'must-revalidate')
352 self.end_headers()
353
maruel@google.come250a9b2009-03-10 17:39:46 +0000354 self.wfile.write('<html><head><title>%s</title></head></html>' %
355 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000356
357 return True
358
359 def CacheMustRevalidateMaxAgeHandler(self):
360 """This request handler yields a page with the title set to the current
361 system time, and does not allow caching event though max-age of 60
362 seconds is specified."""
363
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000364 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000365 return False
366
367 self.send_response(200)
368 self.send_header('Content-type', 'text/html')
369 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
370 self.end_headers()
371
maruel@google.come250a9b2009-03-10 17:39:46 +0000372 self.wfile.write('<html><head><title>%s</title></head></html>' %
373 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000374
375 return True
376
initial.commit94958cf2008-07-26 22:42:52 +0000377 def CacheNoStoreHandler(self):
378 """This request handler yields a page with the title set to the current
379 system time, and does not allow the page to be stored."""
380
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000381 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000382 return False
383
384 self.send_response(200)
385 self.send_header('Content-type', 'text/html')
386 self.send_header('Cache-Control', 'no-store')
387 self.end_headers()
388
maruel@google.come250a9b2009-03-10 17:39:46 +0000389 self.wfile.write('<html><head><title>%s</title></head></html>' %
390 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000391
392 return True
393
394 def CacheNoStoreMaxAgeHandler(self):
395 """This request handler yields a page with the title set to the current
396 system time, and does not allow the page to be stored even though max-age
397 of 60 seconds is specified."""
398
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000399 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000400 return False
401
402 self.send_response(200)
403 self.send_header('Content-type', 'text/html')
404 self.send_header('Cache-Control', 'max-age=60, no-store')
405 self.end_headers()
406
maruel@google.come250a9b2009-03-10 17:39:46 +0000407 self.wfile.write('<html><head><title>%s</title></head></html>' %
408 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000409
410 return True
411
412
413 def CacheNoTransformHandler(self):
414 """This request handler yields a page with the title set to the current
415 system time, and does not allow the content to transformed during
416 user-agent caching"""
417
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000418 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000419 return False
420
421 self.send_response(200)
422 self.send_header('Content-type', 'text/html')
423 self.send_header('Cache-Control', 'no-transform')
424 self.end_headers()
425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self.wfile.write('<html><head><title>%s</title></head></html>' %
427 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000428
429 return True
430
431 def EchoHeader(self):
432 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000433 """The only difference between this function and the EchoHeaderOverride"""
434 """function is in the parameter being passed to the helper function"""
435 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000436
ananta@chromium.org219b2062009-10-23 16:09:41 +0000437 def EchoHeaderOverride(self):
438 """This handler echoes back the value of a specific request header."""
439 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
440 """IE to issue HTTP requests using the host network stack."""
441 """The Accept and Charset tests which expect the server to echo back"""
442 """the corresponding headers fail here as IE returns cached responses"""
443 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
444 """treats this request as a new request and does not cache it."""
445 return self.EchoHeaderHelper("/echoheaderoverride")
446
447 def EchoHeaderHelper(self, echo_header):
448 """This function echoes back the value of the request header passed in."""
449 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000450 return False
451
452 query_char = self.path.find('?')
453 if query_char != -1:
454 header_name = self.path[query_char+1:]
455
456 self.send_response(200)
457 self.send_header('Content-type', 'text/plain')
458 self.send_header('Cache-control', 'max-age=60000')
459 # insert a vary header to properly indicate that the cachability of this
460 # request is subject to value of the request header being echoed.
461 if len(header_name) > 0:
462 self.send_header('Vary', header_name)
463 self.end_headers()
464
465 if len(header_name) > 0:
466 self.wfile.write(self.headers.getheader(header_name))
467
468 return True
469
470 def EchoHandler(self):
471 """This handler just echoes back the payload of the request, for testing
472 form submission."""
473
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000474 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000475 return False
476
477 self.send_response(200)
478 self.send_header('Content-type', 'text/html')
479 self.end_headers()
480 length = int(self.headers.getheader('content-length'))
481 request = self.rfile.read(length)
482 self.wfile.write(request)
483 return True
484
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000485 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000486 """This is handler dumps the content of POST/PUT request to a disk file
487 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000488
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000489 prefix='/writefile/'
490 if not self.path.startswith(prefix):
491 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000492
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000493 file_name = self.path[len(prefix):]
494
495 # do not allow fancy chars in file name
496 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
497 if len(file_name) and file_name[0] != '.':
498 path = os.path.join(self.server.data_dir, 'dump', file_name);
499 length = int(self.headers.getheader('content-length'))
500 request = self.rfile.read(length)
501 f = open(path, "wb")
502 f.write(request);
503 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000504
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000505 self.send_response(200)
506 self.send_header('Content-type', 'text/html')
507 self.end_headers()
508 self.wfile.write('<html>%s</html>' % file_name)
509 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000510
initial.commit94958cf2008-07-26 22:42:52 +0000511 def EchoTitleHandler(self):
512 """This handler is like Echo, but sets the page title to the request."""
513
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000514 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000515 return False
516
517 self.send_response(200)
518 self.send_header('Content-type', 'text/html')
519 self.end_headers()
520 length = int(self.headers.getheader('content-length'))
521 request = self.rfile.read(length)
522 self.wfile.write('<html><head><title>')
523 self.wfile.write(request)
524 self.wfile.write('</title></head></html>')
525 return True
526
527 def EchoAllHandler(self):
528 """This handler yields a (more) human-readable page listing information
529 about the request header & contents."""
530
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000531 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000532 return False
533
534 self.send_response(200)
535 self.send_header('Content-type', 'text/html')
536 self.end_headers()
537 self.wfile.write('<html><head><style>'
538 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
539 '</style></head><body>'
540 '<div style="float: right">'
541 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
542 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000543
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000544 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000545 length = int(self.headers.getheader('content-length'))
546 qs = self.rfile.read(length)
547 params = cgi.parse_qs(qs, keep_blank_values=1)
548
549 for param in params:
550 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000551
552 self.wfile.write('</pre>')
553
554 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
555
556 self.wfile.write('</body></html>')
557 return True
558
559 def DownloadHandler(self):
560 """This handler sends a downloadable file with or without reporting
561 the size (6K)."""
562
563 if self.path.startswith("/download-unknown-size"):
564 send_length = False
565 elif self.path.startswith("/download-known-size"):
566 send_length = True
567 else:
568 return False
569
570 #
571 # The test which uses this functionality is attempting to send
572 # small chunks of data to the client. Use a fairly large buffer
573 # so that we'll fill chrome's IO buffer enough to force it to
574 # actually write the data.
575 # See also the comments in the client-side of this test in
576 # download_uitest.cc
577 #
578 size_chunk1 = 35*1024
579 size_chunk2 = 10*1024
580
581 self.send_response(200)
582 self.send_header('Content-type', 'application/octet-stream')
583 self.send_header('Cache-Control', 'max-age=0')
584 if send_length:
585 self.send_header('Content-Length', size_chunk1 + size_chunk2)
586 self.end_headers()
587
588 # First chunk of data:
589 self.wfile.write("*" * size_chunk1)
590 self.wfile.flush()
591
592 # handle requests until one of them clears this flag.
593 self.server.waitForDownload = True
594 while self.server.waitForDownload:
595 self.server.handle_request()
596
597 # Second chunk of data:
598 self.wfile.write("*" * size_chunk2)
599 return True
600
601 def DownloadFinishHandler(self):
602 """This handler just tells the server to finish the current download."""
603
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000604 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000605 return False
606
607 self.server.waitForDownload = False
608 self.send_response(200)
609 self.send_header('Content-type', 'text/html')
610 self.send_header('Cache-Control', 'max-age=0')
611 self.end_headers()
612 return True
613
614 def FileHandler(self):
615 """This handler sends the contents of the requested file. Wow, it's like
616 a real webserver!"""
617
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000618 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000619 if not self.path.startswith(prefix):
620 return False
621
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000622 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000623 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000624 self.rfile.read(int(self.headers.getheader('content-length')))
625
initial.commit94958cf2008-07-26 22:42:52 +0000626 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000627 if file.find('?') > -1:
628 # Ignore the query parameters entirely.
629 url, querystring = file.split('?')
630 else:
631 url = file
632 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000633 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000634 if os.path.isdir(path):
635 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000636
637 if not os.path.isfile(path):
638 print "File not found " + file + " full path:" + path
639 self.send_error(404)
640 return True
641
642 f = open(path, "rb")
643 data = f.read()
644 f.close()
645
646 # If file.mock-http-headers exists, it contains the headers we
647 # should send. Read them in and parse them.
648 headers_path = path + '.mock-http-headers'
649 if os.path.isfile(headers_path):
650 f = open(headers_path, "r")
651
652 # "HTTP/1.1 200 OK"
653 response = f.readline()
654 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
655 self.send_response(int(status_code))
656
657 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000658 header_values = re.findall('(\S+):\s*(.*)', line)
659 if len(header_values) > 0:
660 # "name: value"
661 name, value = header_values[0]
662 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000663 f.close()
664 else:
665 # Could be more generic once we support mime-type sniffing, but for
666 # now we need to set it explicitly.
667 self.send_response(200)
668 self.send_header('Content-type', self.GetMIMETypeFromName(file))
669 self.send_header('Content-Length', len(data))
670 self.end_headers()
671
672 self.wfile.write(data)
673
674 return True
675
676 def RealFileWithCommonHeaderHandler(self):
677 """This handler sends the contents of the requested file without the pseudo
678 http head!"""
679
680 prefix='/realfiles/'
681 if not self.path.startswith(prefix):
682 return False
683
684 file = self.path[len(prefix):]
685 path = os.path.join(self.server.data_dir, file)
686
687 try:
688 f = open(path, "rb")
689 data = f.read()
690 f.close()
691
692 # just simply set the MIME as octal stream
693 self.send_response(200)
694 self.send_header('Content-type', 'application/octet-stream')
695 self.end_headers()
696
697 self.wfile.write(data)
698 except:
699 self.send_error(404)
700
701 return True
702
703 def RealBZ2FileWithCommonHeaderHandler(self):
704 """This handler sends the bzip2 contents of the requested file with
705 corresponding Content-Encoding field in http head!"""
706
707 prefix='/realbz2files/'
708 if not self.path.startswith(prefix):
709 return False
710
711 parts = self.path.split('?')
712 file = parts[0][len(prefix):]
713 path = os.path.join(self.server.data_dir, file) + '.bz2'
714
715 if len(parts) > 1:
716 options = parts[1]
717 else:
718 options = ''
719
720 try:
721 self.send_response(200)
722 accept_encoding = self.headers.get("Accept-Encoding")
723 if accept_encoding.find("bzip2") != -1:
724 f = open(path, "rb")
725 data = f.read()
726 f.close()
727 self.send_header('Content-Encoding', 'bzip2')
728 self.send_header('Content-type', 'application/x-bzip2')
729 self.end_headers()
730 if options == 'incremental-header':
731 self.wfile.write(data[:1])
732 self.wfile.flush()
733 time.sleep(1.0)
734 self.wfile.write(data[1:])
735 else:
736 self.wfile.write(data)
737 else:
738 """client do not support bzip2 format, send pseudo content
739 """
740 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
741 self.end_headers()
742 self.wfile.write("you do not support bzip2 encoding")
743 except:
744 self.send_error(404)
745
746 return True
747
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000748 def SetCookieHandler(self):
749 """This handler just sets a cookie, for testing cookie handling."""
750
751 if not self._ShouldHandleRequest("/set-cookie"):
752 return False
753
754 query_char = self.path.find('?')
755 if query_char != -1:
756 cookie_values = self.path[query_char + 1:].split('&')
757 else:
758 cookie_values = ("",)
759 self.send_response(200)
760 self.send_header('Content-type', 'text/html')
761 for cookie_value in cookie_values:
762 self.send_header('Set-Cookie', '%s' % cookie_value)
763 self.end_headers()
764 for cookie_value in cookie_values:
765 self.wfile.write('%s' % cookie_value)
766 return True
767
initial.commit94958cf2008-07-26 22:42:52 +0000768 def AuthBasicHandler(self):
769 """This handler tests 'Basic' authentication. It just sends a page with
770 title 'user/pass' if you succeed."""
771
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000772 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000773 return False
774
775 username = userpass = password = b64str = ""
776
ericroman@google.com239b4d82009-03-27 04:00:22 +0000777 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
778
initial.commit94958cf2008-07-26 22:42:52 +0000779 auth = self.headers.getheader('authorization')
780 try:
781 if not auth:
782 raise Exception('no auth')
783 b64str = re.findall(r'Basic (\S+)', auth)[0]
784 userpass = base64.b64decode(b64str)
785 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
786 if password != 'secret':
787 raise Exception('wrong password')
788 except Exception, e:
789 # Authentication failed.
790 self.send_response(401)
791 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
792 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000793 if set_cookie_if_challenged:
794 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000795 self.end_headers()
796 self.wfile.write('<html><head>')
797 self.wfile.write('<title>Denied: %s</title>' % e)
798 self.wfile.write('</head><body>')
799 self.wfile.write('auth=%s<p>' % auth)
800 self.wfile.write('b64str=%s<p>' % b64str)
801 self.wfile.write('username: %s<p>' % username)
802 self.wfile.write('userpass: %s<p>' % userpass)
803 self.wfile.write('password: %s<p>' % password)
804 self.wfile.write('You sent:<br>%s<p>' % self.headers)
805 self.wfile.write('</body></html>')
806 return True
807
808 # Authentication successful. (Return a cachable response to allow for
809 # testing cached pages that require authentication.)
810 if_none_match = self.headers.getheader('if-none-match')
811 if if_none_match == "abc":
812 self.send_response(304)
813 self.end_headers()
814 else:
815 self.send_response(200)
816 self.send_header('Content-type', 'text/html')
817 self.send_header('Cache-control', 'max-age=60000')
818 self.send_header('Etag', 'abc')
819 self.end_headers()
820 self.wfile.write('<html><head>')
821 self.wfile.write('<title>%s/%s</title>' % (username, password))
822 self.wfile.write('</head><body>')
823 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000824 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000825 self.wfile.write('</body></html>')
826
827 return True
828
tonyg@chromium.org75054202010-03-31 22:06:10 +0000829 def GetNonce(self, force_reset=False):
830 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000831
tonyg@chromium.org75054202010-03-31 22:06:10 +0000832 This is a fake implementation. A real implementation would only use a given
833 nonce a single time (hence the name n-once). However, for the purposes of
834 unittesting, we don't care about the security of the nonce.
835
836 Args:
837 force_reset: Iff set, the nonce will be changed. Useful for testing the
838 "stale" response.
839 """
840 if force_reset or not self.server.nonce_time:
841 self.server.nonce_time = time.time()
842 return _new_md5('privatekey%s%d' %
843 (self.path, self.server.nonce_time)).hexdigest()
844
845 def AuthDigestHandler(self):
846 """This handler tests 'Digest' authentication.
847
848 It just sends a page with title 'user/pass' if you succeed.
849
850 A stale response is sent iff "stale" is present in the request path.
851 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000852 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000853 return False
854
tonyg@chromium.org75054202010-03-31 22:06:10 +0000855 stale = 'stale' in self.path
856 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000857 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000858 password = 'secret'
859 realm = 'testrealm'
860
861 auth = self.headers.getheader('authorization')
862 pairs = {}
863 try:
864 if not auth:
865 raise Exception('no auth')
866 if not auth.startswith('Digest'):
867 raise Exception('not digest')
868 # Pull out all the name="value" pairs as a dictionary.
869 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
870
871 # Make sure it's all valid.
872 if pairs['nonce'] != nonce:
873 raise Exception('wrong nonce')
874 if pairs['opaque'] != opaque:
875 raise Exception('wrong opaque')
876
877 # Check the 'response' value and make sure it matches our magic hash.
878 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000879 hash_a1 = _new_md5(
880 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000881 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000882 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000883 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000884 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
885 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000886 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000887
888 if pairs['response'] != response:
889 raise Exception('wrong password')
890 except Exception, e:
891 # Authentication failed.
892 self.send_response(401)
893 hdr = ('Digest '
894 'realm="%s", '
895 'domain="/", '
896 'qop="auth", '
897 'algorithm=MD5, '
898 'nonce="%s", '
899 'opaque="%s"') % (realm, nonce, opaque)
900 if stale:
901 hdr += ', stale="TRUE"'
902 self.send_header('WWW-Authenticate', hdr)
903 self.send_header('Content-type', 'text/html')
904 self.end_headers()
905 self.wfile.write('<html><head>')
906 self.wfile.write('<title>Denied: %s</title>' % e)
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('You sent:<br>%s<p>' % self.headers)
911 self.wfile.write('We are replying:<br>%s<p>' % hdr)
912 self.wfile.write('</body></html>')
913 return True
914
915 # Authentication successful.
916 self.send_response(200)
917 self.send_header('Content-type', 'text/html')
918 self.end_headers()
919 self.wfile.write('<html><head>')
920 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
921 self.wfile.write('</head><body>')
922 self.wfile.write('auth=%s<p>' % auth)
923 self.wfile.write('pairs=%s<p>' % pairs)
924 self.wfile.write('</body></html>')
925
926 return True
927
928 def SlowServerHandler(self):
929 """Wait for the user suggested time before responding. The syntax is
930 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000931 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000932 return False
933 query_char = self.path.find('?')
934 wait_sec = 1.0
935 if query_char >= 0:
936 try:
937 wait_sec = int(self.path[query_char + 1:])
938 except ValueError:
939 pass
940 time.sleep(wait_sec)
941 self.send_response(200)
942 self.send_header('Content-type', 'text/plain')
943 self.end_headers()
944 self.wfile.write("waited %d seconds" % wait_sec)
945 return True
946
947 def ContentTypeHandler(self):
948 """Returns a string of html with the given content type. E.g.,
949 /contenttype?text/css returns an html file with the Content-Type
950 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000951 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000952 return False
953 query_char = self.path.find('?')
954 content_type = self.path[query_char + 1:].strip()
955 if not content_type:
956 content_type = 'text/html'
957 self.send_response(200)
958 self.send_header('Content-Type', content_type)
959 self.end_headers()
960 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
961 return True
962
963 def ServerRedirectHandler(self):
964 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000965 '/server-redirect?http://foo.bar/asdf' to redirect to
966 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000967
968 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000969 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000970 return False
971
972 query_char = self.path.find('?')
973 if query_char < 0 or len(self.path) <= query_char + 1:
974 self.sendRedirectHelp(test_name)
975 return True
976 dest = self.path[query_char + 1:]
977
978 self.send_response(301) # moved permanently
979 self.send_header('Location', dest)
980 self.send_header('Content-type', 'text/html')
981 self.end_headers()
982 self.wfile.write('<html><head>')
983 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
984
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000985 return True
initial.commit94958cf2008-07-26 22:42:52 +0000986
987 def ClientRedirectHandler(self):
988 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000989 '/client-redirect?http://foo.bar/asdf' to redirect to
990 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000991
992 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000993 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000994 return False
995
996 query_char = self.path.find('?');
997 if query_char < 0 or len(self.path) <= query_char + 1:
998 self.sendRedirectHelp(test_name)
999 return True
1000 dest = self.path[query_char + 1:]
1001
1002 self.send_response(200)
1003 self.send_header('Content-type', 'text/html')
1004 self.end_headers()
1005 self.wfile.write('<html><head>')
1006 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1007 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1008
1009 return True
1010
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001011 def ChromiumSyncTimeHandler(self):
1012 """Handle Chromium sync .../time requests.
1013
1014 The syncer sometimes checks server reachability by examining /time.
1015 """
1016 test_name = "/chromiumsync/time"
1017 if not self._ShouldHandleRequest(test_name):
1018 return False
1019
1020 self.send_response(200)
1021 self.send_header('Content-type', 'text/html')
1022 self.end_headers()
1023 return True
1024
skrul@chromium.orgfff501f2010-08-13 21:01:52 +00001025 def ChromiumSyncConfigureHandler(self):
1026 """Handle updating the configuration of the sync server.
1027
1028 The name and value pairs of the post payload will update the
1029 configuration of the sync server. Supported tuples are:
1030 user_email,<email address> - Sets the email for the fake user account
1031 """
1032 test_name = "/chromiumsync/configure"
1033 if not self._ShouldHandleRequest(test_name):
1034 return False
1035
1036 length = int(self.headers.getheader('content-length'))
1037 raw_request = self.rfile.read(length)
1038 config = cgi.parse_qs(raw_request, keep_blank_values=1)
1039
1040 success = self._sync_handler.HandleConfigure(config)
1041 if success:
1042 self.send_response(200)
1043 else:
1044 self.send_response(500)
1045 self.end_headers()
1046 return True
1047
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001048 def ChromiumSyncCommandHandler(self):
1049 """Handle a chromiumsync command arriving via http.
1050
1051 This covers all sync protocol commands: authentication, getupdates, and
1052 commit.
1053 """
1054 test_name = "/chromiumsync/command"
1055 if not self._ShouldHandleRequest(test_name):
1056 return False
1057
1058 length = int(self.headers.getheader('content-length'))
1059 raw_request = self.rfile.read(length)
1060
pathorn@chromium.org44920122010-07-27 18:25:35 +00001061 if not self.server._sync_handler:
1062 import chromiumsync
1063 self.server._sync_handler = chromiumsync.TestServer()
1064 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1065 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001066 self.send_response(http_response)
1067 self.end_headers()
1068 self.wfile.write(raw_reply)
1069 return True
1070
tony@chromium.org03266982010-03-05 03:18:42 +00001071 def MultipartHandler(self):
1072 """Send a multipart response (10 text/html pages)."""
1073 test_name = "/multipart"
1074 if not self._ShouldHandleRequest(test_name):
1075 return False
1076
1077 num_frames = 10
1078 bound = '12345'
1079 self.send_response(200)
1080 self.send_header('Content-type',
1081 'multipart/x-mixed-replace;boundary=' + bound)
1082 self.end_headers()
1083
1084 for i in xrange(num_frames):
1085 self.wfile.write('--' + bound + '\r\n')
1086 self.wfile.write('Content-type: text/html\r\n\r\n')
1087 self.wfile.write('<title>page ' + str(i) + '</title>')
1088 self.wfile.write('page ' + str(i))
1089
1090 self.wfile.write('--' + bound + '--')
1091 return True
1092
initial.commit94958cf2008-07-26 22:42:52 +00001093 def DefaultResponseHandler(self):
1094 """This is the catch-all response handler for requests that aren't handled
1095 by one of the special handlers above.
1096 Note that we specify the content-length as without it the https connection
1097 is not closed properly (and the browser keeps expecting data)."""
1098
1099 contents = "Default response given for path: " + self.path
1100 self.send_response(200)
1101 self.send_header('Content-type', 'text/html')
1102 self.send_header("Content-Length", len(contents))
1103 self.end_headers()
1104 self.wfile.write(contents)
1105 return True
1106
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001107 def RedirectConnectHandler(self):
1108 """Sends a redirect to the CONNECT request for www.redirect.com. This
1109 response is not specified by the RFC, so the browser should not follow
1110 the redirect."""
1111
1112 if (self.path.find("www.redirect.com") < 0):
1113 return False
1114
1115 dest = "http://www.destination.com/foo.js"
1116
1117 self.send_response(302) # moved temporarily
1118 self.send_header('Location', dest)
1119 self.send_header('Connection', 'close')
1120 self.end_headers()
1121 return True
1122
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001123 def ServerAuthConnectHandler(self):
1124 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1125 response doesn't make sense because the proxy server cannot request
1126 server authentication."""
1127
1128 if (self.path.find("www.server-auth.com") < 0):
1129 return False
1130
1131 challenge = 'Basic realm="WallyWorld"'
1132
1133 self.send_response(401) # unauthorized
1134 self.send_header('WWW-Authenticate', challenge)
1135 self.send_header('Connection', 'close')
1136 self.end_headers()
1137 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001138
1139 def DefaultConnectResponseHandler(self):
1140 """This is the catch-all response handler for CONNECT requests that aren't
1141 handled by one of the special handlers above. Real Web servers respond
1142 with 400 to CONNECT requests."""
1143
1144 contents = "Your client has issued a malformed or illegal request."
1145 self.send_response(400) # bad request
1146 self.send_header('Content-type', 'text/html')
1147 self.send_header("Content-Length", len(contents))
1148 self.end_headers()
1149 self.wfile.write(contents)
1150 return True
1151
1152 def do_CONNECT(self):
1153 for handler in self._connect_handlers:
1154 if handler():
1155 return
1156
initial.commit94958cf2008-07-26 22:42:52 +00001157 def do_GET(self):
1158 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001159 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001160 return
1161
1162 def do_POST(self):
1163 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001164 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001165 return
1166
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001167 def do_PUT(self):
1168 for handler in self._put_handlers:
1169 if handler():
1170 return
1171
initial.commit94958cf2008-07-26 22:42:52 +00001172 # called by the redirect handling function when there is no parameter
1173 def sendRedirectHelp(self, redirect_name):
1174 self.send_response(200)
1175 self.send_header('Content-type', 'text/html')
1176 self.end_headers()
1177 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1178 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1179 self.wfile.write('</body></html>')
1180
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001181def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001182 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1183 requests will be stored. If the directory already exists all files and
1184 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001185 dump_dir = os.path.join(data_dir, 'dump');
1186 if os.path.isdir(dump_dir):
1187 shutil.rmtree(dump_dir)
1188 os.mkdir(dump_dir)
1189
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001190def MakeDataDir():
1191 if options.data_dir:
1192 if not os.path.isdir(options.data_dir):
1193 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1194 return None
1195 my_data_dir = options.data_dir
1196 else:
1197 # Create the default path to our data dir, relative to the exe dir.
1198 my_data_dir = os.path.dirname(sys.argv[0])
1199 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001200 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001201
1202 #TODO(ibrar): Must use Find* funtion defined in google\tools
1203 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1204
1205 return my_data_dir
1206
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001207class FileMultiplexer:
1208 def __init__(self, fd1, fd2) :
1209 self.__fd1 = fd1
1210 self.__fd2 = fd2
1211
1212 def __del__(self) :
1213 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1214 self.__fd1.close()
1215 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1216 self.__fd2.close()
1217
1218 def write(self, text) :
1219 self.__fd1.write(text)
1220 self.__fd2.write(text)
1221
1222 def flush(self) :
1223 self.__fd1.flush()
1224 self.__fd2.flush()
1225
initial.commit94958cf2008-07-26 22:42:52 +00001226def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001227 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001228 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1229 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001230
1231 port = options.port
1232
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001233 if options.server_type == SERVER_HTTP:
1234 if options.cert:
1235 # let's make sure the cert file exists.
1236 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001237 print 'specified server cert file not found: ' + options.cert + \
1238 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001239 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001240 for ca_cert in options.ssl_client_ca:
1241 if not os.path.isfile(ca_cert):
1242 print 'specified trusted client CA file not found: ' + ca_cert + \
1243 ' exiting...'
1244 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001245 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001246 options.ssl_client_auth, options.ssl_client_ca)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001247 print 'HTTPS server started on port %d...' % port
1248 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001249 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001250 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001251
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001252 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001253 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001254 server._sync_handler = None
1255
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001256 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001257
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001258 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001259 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001260 my_data_dir = MakeDataDir()
1261
1262 def line_logger(msg):
1263 if (msg.find("kill") >= 0):
1264 server.stop = True
1265 print 'shutting down server'
1266 sys.exit(0)
1267
1268 # Instantiate a dummy authorizer for managing 'virtual' users
1269 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1270
1271 # Define a new user having full r/w permissions and a read-only
1272 # anonymous user
1273 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1274
1275 authorizer.add_anonymous(my_data_dir)
1276
1277 # Instantiate FTP handler class
1278 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1279 ftp_handler.authorizer = authorizer
1280 pyftpdlib.ftpserver.logline = line_logger
1281
1282 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001283 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1284 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001285
1286 # Instantiate FTP server class and listen to 127.0.0.1:port
1287 address = ('127.0.0.1', port)
1288 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1289 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001290
1291 try:
1292 server.serve_forever()
1293 except KeyboardInterrupt:
1294 print 'shutting down server'
1295 server.stop = True
1296
1297if __name__ == '__main__':
1298 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001299 option_parser.add_option("-f", '--ftp', action='store_const',
1300 const=SERVER_FTP, default=SERVER_HTTP,
1301 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001302 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001303 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001304 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001305 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001306 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001307 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001308 help='Specify that https should be used, specify '
1309 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001310 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001311 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1312 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001313 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1314 help='Specify that the client certificate request '
1315 'should indicate that it supports the CA contained '
1316 'in the specified certificate file')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001317 option_parser.add_option('', '--file-root-url', default='/files/',
1318 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001319 option_parser.add_option('', '--never-die', default=False,
1320 action="store_true",
1321 help='Prevent the server from dying when visiting '
1322 'a /kill URL. Useful for manually running some '
1323 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001324 options, args = option_parser.parse_args()
1325
1326 sys.exit(main(options, args))