blob: 8a864a86cc52113937581d4770b021b0495f3511 [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 = [
141 self.EchoTitleHandler,
142 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000143 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000144 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000145 self._put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000146 self.EchoTitleHandler,
147 self.EchoAllHandler,
148 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000149
maruel@google.come250a9b2009-03-10 17:39:46 +0000150 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000151 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000152 'gif': 'image/gif',
153 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000154 'jpg' : 'image/jpeg',
155 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000156 }
initial.commit94958cf2008-07-26 22:42:52 +0000157 self._default_mime_type = 'text/html'
158
maruel@google.come250a9b2009-03-10 17:39:46 +0000159 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
160 client_address,
161 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000162
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000163 def log_request(self, *args, **kwargs):
164 # Disable request logging to declutter test log output.
165 pass
166
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000167 def _ShouldHandleRequest(self, handler_name):
168 """Determines if the path can be handled by the handler.
169
170 We consider a handler valid if the path begins with the
171 handler name. It can optionally be followed by "?*", "/*".
172 """
173
174 pattern = re.compile('%s($|\?|/).*' % handler_name)
175 return pattern.match(self.path)
176
initial.commit94958cf2008-07-26 22:42:52 +0000177 def GetMIMETypeFromName(self, file_name):
178 """Returns the mime type for the specified file_name. So far it only looks
179 at the file extension."""
180
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000181 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000182 if len(extension) == 0:
183 # no extension.
184 return self._default_mime_type
185
ericroman@google.comc17ca532009-05-07 03:51:05 +0000186 # extension starts with a dot, so we need to remove it
187 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000188
189 def KillHandler(self):
190 """This request handler kills the server, for use when we're done"
191 with the a particular test."""
192
193 if (self.path.find("kill") < 0):
194 return False
195
196 self.send_response(200)
197 self.send_header('Content-type', 'text/html')
198 self.send_header('Cache-Control', 'max-age=0')
199 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000200 if options.never_die:
201 self.wfile.write('I cannot die!! BWAHAHA')
202 else:
203 self.wfile.write('Goodbye cruel world!')
204 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000205
206 return True
207
208 def NoCacheMaxAgeTimeHandler(self):
209 """This request handler yields a page with the title set to the current
210 system time, and no caching requested."""
211
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000212 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000213 return False
214
215 self.send_response(200)
216 self.send_header('Cache-Control', 'max-age=0')
217 self.send_header('Content-type', 'text/html')
218 self.end_headers()
219
maruel@google.come250a9b2009-03-10 17:39:46 +0000220 self.wfile.write('<html><head><title>%s</title></head></html>' %
221 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000222
223 return True
224
225 def NoCacheTimeHandler(self):
226 """This request handler yields a page with the title set to the current
227 system time, and no caching requested."""
228
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000229 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000230 return False
231
232 self.send_response(200)
233 self.send_header('Cache-Control', 'no-cache')
234 self.send_header('Content-type', 'text/html')
235 self.end_headers()
236
maruel@google.come250a9b2009-03-10 17:39:46 +0000237 self.wfile.write('<html><head><title>%s</title></head></html>' %
238 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000239
240 return True
241
242 def CacheTimeHandler(self):
243 """This request handler yields a page with the title set to the current
244 system time, and allows caching for one minute."""
245
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000246 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000247 return False
248
249 self.send_response(200)
250 self.send_header('Cache-Control', 'max-age=60')
251 self.send_header('Content-type', 'text/html')
252 self.end_headers()
253
maruel@google.come250a9b2009-03-10 17:39:46 +0000254 self.wfile.write('<html><head><title>%s</title></head></html>' %
255 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000256
257 return True
258
259 def CacheExpiresHandler(self):
260 """This request handler yields a page with the title set to the current
261 system time, and set the page to expire on 1 Jan 2099."""
262
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000263 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000264 return False
265
266 self.send_response(200)
267 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
268 self.send_header('Content-type', 'text/html')
269 self.end_headers()
270
maruel@google.come250a9b2009-03-10 17:39:46 +0000271 self.wfile.write('<html><head><title>%s</title></head></html>' %
272 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000273
274 return True
275
276 def CacheProxyRevalidateHandler(self):
277 """This request handler yields a page with the title set to the current
278 system time, and allows caching for 60 seconds"""
279
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000280 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000281 return False
282
283 self.send_response(200)
284 self.send_header('Content-type', 'text/html')
285 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
286 self.end_headers()
287
maruel@google.come250a9b2009-03-10 17:39:46 +0000288 self.wfile.write('<html><head><title>%s</title></head></html>' %
289 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000290
291 return True
292
293 def CachePrivateHandler(self):
294 """This request handler yields a page with the title set to the current
295 system time, and allows caching for 5 seconds."""
296
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000297 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000298 return False
299
300 self.send_response(200)
301 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000302 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000303 self.end_headers()
304
maruel@google.come250a9b2009-03-10 17:39:46 +0000305 self.wfile.write('<html><head><title>%s</title></head></html>' %
306 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000307
308 return True
309
310 def CachePublicHandler(self):
311 """This request handler yields a page with the title set to the current
312 system time, and allows caching for 5 seconds."""
313
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000314 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000315 return False
316
317 self.send_response(200)
318 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000319 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000320 self.end_headers()
321
maruel@google.come250a9b2009-03-10 17:39:46 +0000322 self.wfile.write('<html><head><title>%s</title></head></html>' %
323 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000324
325 return True
326
327 def CacheSMaxAgeHandler(self):
328 """This request handler yields a page with the title set to the current
329 system time, and does not allow for caching."""
330
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000331 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000332 return False
333
334 self.send_response(200)
335 self.send_header('Content-type', 'text/html')
336 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
337 self.end_headers()
338
maruel@google.come250a9b2009-03-10 17:39:46 +0000339 self.wfile.write('<html><head><title>%s</title></head></html>' %
340 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000341
342 return True
343
344 def CacheMustRevalidateHandler(self):
345 """This request handler yields a page with the title set to the current
346 system time, and does not allow caching."""
347
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000348 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000349 return False
350
351 self.send_response(200)
352 self.send_header('Content-type', 'text/html')
353 self.send_header('Cache-Control', 'must-revalidate')
354 self.end_headers()
355
maruel@google.come250a9b2009-03-10 17:39:46 +0000356 self.wfile.write('<html><head><title>%s</title></head></html>' %
357 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000358
359 return True
360
361 def CacheMustRevalidateMaxAgeHandler(self):
362 """This request handler yields a page with the title set to the current
363 system time, and does not allow caching event though max-age of 60
364 seconds is specified."""
365
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000366 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000367 return False
368
369 self.send_response(200)
370 self.send_header('Content-type', 'text/html')
371 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
372 self.end_headers()
373
maruel@google.come250a9b2009-03-10 17:39:46 +0000374 self.wfile.write('<html><head><title>%s</title></head></html>' %
375 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000376
377 return True
378
initial.commit94958cf2008-07-26 22:42:52 +0000379 def CacheNoStoreHandler(self):
380 """This request handler yields a page with the title set to the current
381 system time, and does not allow the page to be stored."""
382
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000383 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000384 return False
385
386 self.send_response(200)
387 self.send_header('Content-type', 'text/html')
388 self.send_header('Cache-Control', 'no-store')
389 self.end_headers()
390
maruel@google.come250a9b2009-03-10 17:39:46 +0000391 self.wfile.write('<html><head><title>%s</title></head></html>' %
392 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000393
394 return True
395
396 def CacheNoStoreMaxAgeHandler(self):
397 """This request handler yields a page with the title set to the current
398 system time, and does not allow the page to be stored even though max-age
399 of 60 seconds is specified."""
400
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000401 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000402 return False
403
404 self.send_response(200)
405 self.send_header('Content-type', 'text/html')
406 self.send_header('Cache-Control', 'max-age=60, no-store')
407 self.end_headers()
408
maruel@google.come250a9b2009-03-10 17:39:46 +0000409 self.wfile.write('<html><head><title>%s</title></head></html>' %
410 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000411
412 return True
413
414
415 def CacheNoTransformHandler(self):
416 """This request handler yields a page with the title set to the current
417 system time, and does not allow the content to transformed during
418 user-agent caching"""
419
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000420 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000421 return False
422
423 self.send_response(200)
424 self.send_header('Content-type', 'text/html')
425 self.send_header('Cache-Control', 'no-transform')
426 self.end_headers()
427
maruel@google.come250a9b2009-03-10 17:39:46 +0000428 self.wfile.write('<html><head><title>%s</title></head></html>' %
429 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000430
431 return True
432
433 def EchoHeader(self):
434 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000435 """The only difference between this function and the EchoHeaderOverride"""
436 """function is in the parameter being passed to the helper function"""
437 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000438
ananta@chromium.org219b2062009-10-23 16:09:41 +0000439 def EchoHeaderOverride(self):
440 """This handler echoes back the value of a specific request header."""
441 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
442 """IE to issue HTTP requests using the host network stack."""
443 """The Accept and Charset tests which expect the server to echo back"""
444 """the corresponding headers fail here as IE returns cached responses"""
445 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
446 """treats this request as a new request and does not cache it."""
447 return self.EchoHeaderHelper("/echoheaderoverride")
448
449 def EchoHeaderHelper(self, echo_header):
450 """This function echoes back the value of the request header passed in."""
451 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000452 return False
453
454 query_char = self.path.find('?')
455 if query_char != -1:
456 header_name = self.path[query_char+1:]
457
458 self.send_response(200)
459 self.send_header('Content-type', 'text/plain')
460 self.send_header('Cache-control', 'max-age=60000')
461 # insert a vary header to properly indicate that the cachability of this
462 # request is subject to value of the request header being echoed.
463 if len(header_name) > 0:
464 self.send_header('Vary', header_name)
465 self.end_headers()
466
467 if len(header_name) > 0:
468 self.wfile.write(self.headers.getheader(header_name))
469
470 return True
471
472 def EchoHandler(self):
473 """This handler just echoes back the payload of the request, for testing
474 form submission."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
480 self.send_header('Content-type', 'text/html')
481 self.end_headers()
482 length = int(self.headers.getheader('content-length'))
483 request = self.rfile.read(length)
484 self.wfile.write(request)
485 return True
486
487 def EchoTitleHandler(self):
488 """This handler is like Echo, but sets the page title to the request."""
489
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000490 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000491 return False
492
493 self.send_response(200)
494 self.send_header('Content-type', 'text/html')
495 self.end_headers()
496 length = int(self.headers.getheader('content-length'))
497 request = self.rfile.read(length)
498 self.wfile.write('<html><head><title>')
499 self.wfile.write(request)
500 self.wfile.write('</title></head></html>')
501 return True
502
503 def EchoAllHandler(self):
504 """This handler yields a (more) human-readable page listing information
505 about the request header & contents."""
506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
511 self.send_header('Content-type', 'text/html')
512 self.end_headers()
513 self.wfile.write('<html><head><style>'
514 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
515 '</style></head><body>'
516 '<div style="float: right">'
517 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
518 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000519
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000520 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000521 length = int(self.headers.getheader('content-length'))
522 qs = self.rfile.read(length)
523 params = cgi.parse_qs(qs, keep_blank_values=1)
524
525 for param in params:
526 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000527
528 self.wfile.write('</pre>')
529
530 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
531
532 self.wfile.write('</body></html>')
533 return True
534
535 def DownloadHandler(self):
536 """This handler sends a downloadable file with or without reporting
537 the size (6K)."""
538
539 if self.path.startswith("/download-unknown-size"):
540 send_length = False
541 elif self.path.startswith("/download-known-size"):
542 send_length = True
543 else:
544 return False
545
546 #
547 # The test which uses this functionality is attempting to send
548 # small chunks of data to the client. Use a fairly large buffer
549 # so that we'll fill chrome's IO buffer enough to force it to
550 # actually write the data.
551 # See also the comments in the client-side of this test in
552 # download_uitest.cc
553 #
554 size_chunk1 = 35*1024
555 size_chunk2 = 10*1024
556
557 self.send_response(200)
558 self.send_header('Content-type', 'application/octet-stream')
559 self.send_header('Cache-Control', 'max-age=0')
560 if send_length:
561 self.send_header('Content-Length', size_chunk1 + size_chunk2)
562 self.end_headers()
563
564 # First chunk of data:
565 self.wfile.write("*" * size_chunk1)
566 self.wfile.flush()
567
568 # handle requests until one of them clears this flag.
569 self.server.waitForDownload = True
570 while self.server.waitForDownload:
571 self.server.handle_request()
572
573 # Second chunk of data:
574 self.wfile.write("*" * size_chunk2)
575 return True
576
577 def DownloadFinishHandler(self):
578 """This handler just tells the server to finish the current download."""
579
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000580 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000581 return False
582
583 self.server.waitForDownload = False
584 self.send_response(200)
585 self.send_header('Content-type', 'text/html')
586 self.send_header('Cache-Control', 'max-age=0')
587 self.end_headers()
588 return True
589
590 def FileHandler(self):
591 """This handler sends the contents of the requested file. Wow, it's like
592 a real webserver!"""
593
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000594 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000595 if not self.path.startswith(prefix):
596 return False
597
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000598 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000599 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000600 self.rfile.read(int(self.headers.getheader('content-length')))
601
initial.commit94958cf2008-07-26 22:42:52 +0000602 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000603 if file.find('?') > -1:
604 # Ignore the query parameters entirely.
605 url, querystring = file.split('?')
606 else:
607 url = file
608 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000609 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000610 if os.path.isdir(path):
611 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000612
613 if not os.path.isfile(path):
614 print "File not found " + file + " full path:" + path
615 self.send_error(404)
616 return True
617
618 f = open(path, "rb")
619 data = f.read()
620 f.close()
621
622 # If file.mock-http-headers exists, it contains the headers we
623 # should send. Read them in and parse them.
624 headers_path = path + '.mock-http-headers'
625 if os.path.isfile(headers_path):
626 f = open(headers_path, "r")
627
628 # "HTTP/1.1 200 OK"
629 response = f.readline()
630 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
631 self.send_response(int(status_code))
632
633 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000634 header_values = re.findall('(\S+):\s*(.*)', line)
635 if len(header_values) > 0:
636 # "name: value"
637 name, value = header_values[0]
638 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000639 f.close()
640 else:
641 # Could be more generic once we support mime-type sniffing, but for
642 # now we need to set it explicitly.
643 self.send_response(200)
644 self.send_header('Content-type', self.GetMIMETypeFromName(file))
645 self.send_header('Content-Length', len(data))
646 self.end_headers()
647
648 self.wfile.write(data)
649
650 return True
651
652 def RealFileWithCommonHeaderHandler(self):
653 """This handler sends the contents of the requested file without the pseudo
654 http head!"""
655
656 prefix='/realfiles/'
657 if not self.path.startswith(prefix):
658 return False
659
660 file = self.path[len(prefix):]
661 path = os.path.join(self.server.data_dir, file)
662
663 try:
664 f = open(path, "rb")
665 data = f.read()
666 f.close()
667
668 # just simply set the MIME as octal stream
669 self.send_response(200)
670 self.send_header('Content-type', 'application/octet-stream')
671 self.end_headers()
672
673 self.wfile.write(data)
674 except:
675 self.send_error(404)
676
677 return True
678
679 def RealBZ2FileWithCommonHeaderHandler(self):
680 """This handler sends the bzip2 contents of the requested file with
681 corresponding Content-Encoding field in http head!"""
682
683 prefix='/realbz2files/'
684 if not self.path.startswith(prefix):
685 return False
686
687 parts = self.path.split('?')
688 file = parts[0][len(prefix):]
689 path = os.path.join(self.server.data_dir, file) + '.bz2'
690
691 if len(parts) > 1:
692 options = parts[1]
693 else:
694 options = ''
695
696 try:
697 self.send_response(200)
698 accept_encoding = self.headers.get("Accept-Encoding")
699 if accept_encoding.find("bzip2") != -1:
700 f = open(path, "rb")
701 data = f.read()
702 f.close()
703 self.send_header('Content-Encoding', 'bzip2')
704 self.send_header('Content-type', 'application/x-bzip2')
705 self.end_headers()
706 if options == 'incremental-header':
707 self.wfile.write(data[:1])
708 self.wfile.flush()
709 time.sleep(1.0)
710 self.wfile.write(data[1:])
711 else:
712 self.wfile.write(data)
713 else:
714 """client do not support bzip2 format, send pseudo content
715 """
716 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
717 self.end_headers()
718 self.wfile.write("you do not support bzip2 encoding")
719 except:
720 self.send_error(404)
721
722 return True
723
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000724 def SetCookieHandler(self):
725 """This handler just sets a cookie, for testing cookie handling."""
726
727 if not self._ShouldHandleRequest("/set-cookie"):
728 return False
729
730 query_char = self.path.find('?')
731 if query_char != -1:
732 cookie_values = self.path[query_char + 1:].split('&')
733 else:
734 cookie_values = ("",)
735 self.send_response(200)
736 self.send_header('Content-type', 'text/html')
737 for cookie_value in cookie_values:
738 self.send_header('Set-Cookie', '%s' % cookie_value)
739 self.end_headers()
740 for cookie_value in cookie_values:
741 self.wfile.write('%s' % cookie_value)
742 return True
743
initial.commit94958cf2008-07-26 22:42:52 +0000744 def AuthBasicHandler(self):
745 """This handler tests 'Basic' authentication. It just sends a page with
746 title 'user/pass' if you succeed."""
747
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000748 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000749 return False
750
751 username = userpass = password = b64str = ""
752
ericroman@google.com239b4d82009-03-27 04:00:22 +0000753 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
754
initial.commit94958cf2008-07-26 22:42:52 +0000755 auth = self.headers.getheader('authorization')
756 try:
757 if not auth:
758 raise Exception('no auth')
759 b64str = re.findall(r'Basic (\S+)', auth)[0]
760 userpass = base64.b64decode(b64str)
761 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
762 if password != 'secret':
763 raise Exception('wrong password')
764 except Exception, e:
765 # Authentication failed.
766 self.send_response(401)
767 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
768 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000769 if set_cookie_if_challenged:
770 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000771 self.end_headers()
772 self.wfile.write('<html><head>')
773 self.wfile.write('<title>Denied: %s</title>' % e)
774 self.wfile.write('</head><body>')
775 self.wfile.write('auth=%s<p>' % auth)
776 self.wfile.write('b64str=%s<p>' % b64str)
777 self.wfile.write('username: %s<p>' % username)
778 self.wfile.write('userpass: %s<p>' % userpass)
779 self.wfile.write('password: %s<p>' % password)
780 self.wfile.write('You sent:<br>%s<p>' % self.headers)
781 self.wfile.write('</body></html>')
782 return True
783
784 # Authentication successful. (Return a cachable response to allow for
785 # testing cached pages that require authentication.)
786 if_none_match = self.headers.getheader('if-none-match')
787 if if_none_match == "abc":
788 self.send_response(304)
789 self.end_headers()
790 else:
791 self.send_response(200)
792 self.send_header('Content-type', 'text/html')
793 self.send_header('Cache-control', 'max-age=60000')
794 self.send_header('Etag', 'abc')
795 self.end_headers()
796 self.wfile.write('<html><head>')
797 self.wfile.write('<title>%s/%s</title>' % (username, password))
798 self.wfile.write('</head><body>')
799 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000800 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000801 self.wfile.write('</body></html>')
802
803 return True
804
tonyg@chromium.org75054202010-03-31 22:06:10 +0000805 def GetNonce(self, force_reset=False):
806 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000807
tonyg@chromium.org75054202010-03-31 22:06:10 +0000808 This is a fake implementation. A real implementation would only use a given
809 nonce a single time (hence the name n-once). However, for the purposes of
810 unittesting, we don't care about the security of the nonce.
811
812 Args:
813 force_reset: Iff set, the nonce will be changed. Useful for testing the
814 "stale" response.
815 """
816 if force_reset or not self.server.nonce_time:
817 self.server.nonce_time = time.time()
818 return _new_md5('privatekey%s%d' %
819 (self.path, self.server.nonce_time)).hexdigest()
820
821 def AuthDigestHandler(self):
822 """This handler tests 'Digest' authentication.
823
824 It just sends a page with title 'user/pass' if you succeed.
825
826 A stale response is sent iff "stale" is present in the request path.
827 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000828 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000829 return False
830
tonyg@chromium.org75054202010-03-31 22:06:10 +0000831 stale = 'stale' in self.path
832 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000833 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000834 password = 'secret'
835 realm = 'testrealm'
836
837 auth = self.headers.getheader('authorization')
838 pairs = {}
839 try:
840 if not auth:
841 raise Exception('no auth')
842 if not auth.startswith('Digest'):
843 raise Exception('not digest')
844 # Pull out all the name="value" pairs as a dictionary.
845 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
846
847 # Make sure it's all valid.
848 if pairs['nonce'] != nonce:
849 raise Exception('wrong nonce')
850 if pairs['opaque'] != opaque:
851 raise Exception('wrong opaque')
852
853 # Check the 'response' value and make sure it matches our magic hash.
854 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000855 hash_a1 = _new_md5(
856 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000857 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000858 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000859 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000860 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
861 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000862 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000863
864 if pairs['response'] != response:
865 raise Exception('wrong password')
866 except Exception, e:
867 # Authentication failed.
868 self.send_response(401)
869 hdr = ('Digest '
870 'realm="%s", '
871 'domain="/", '
872 'qop="auth", '
873 'algorithm=MD5, '
874 'nonce="%s", '
875 'opaque="%s"') % (realm, nonce, opaque)
876 if stale:
877 hdr += ', stale="TRUE"'
878 self.send_header('WWW-Authenticate', hdr)
879 self.send_header('Content-type', 'text/html')
880 self.end_headers()
881 self.wfile.write('<html><head>')
882 self.wfile.write('<title>Denied: %s</title>' % e)
883 self.wfile.write('</head><body>')
884 self.wfile.write('auth=%s<p>' % auth)
885 self.wfile.write('pairs=%s<p>' % pairs)
886 self.wfile.write('You sent:<br>%s<p>' % self.headers)
887 self.wfile.write('We are replying:<br>%s<p>' % hdr)
888 self.wfile.write('</body></html>')
889 return True
890
891 # Authentication successful.
892 self.send_response(200)
893 self.send_header('Content-type', 'text/html')
894 self.end_headers()
895 self.wfile.write('<html><head>')
896 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
897 self.wfile.write('</head><body>')
898 self.wfile.write('auth=%s<p>' % auth)
899 self.wfile.write('pairs=%s<p>' % pairs)
900 self.wfile.write('</body></html>')
901
902 return True
903
904 def SlowServerHandler(self):
905 """Wait for the user suggested time before responding. The syntax is
906 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000907 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000908 return False
909 query_char = self.path.find('?')
910 wait_sec = 1.0
911 if query_char >= 0:
912 try:
913 wait_sec = int(self.path[query_char + 1:])
914 except ValueError:
915 pass
916 time.sleep(wait_sec)
917 self.send_response(200)
918 self.send_header('Content-type', 'text/plain')
919 self.end_headers()
920 self.wfile.write("waited %d seconds" % wait_sec)
921 return True
922
923 def ContentTypeHandler(self):
924 """Returns a string of html with the given content type. E.g.,
925 /contenttype?text/css returns an html file with the Content-Type
926 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000927 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000928 return False
929 query_char = self.path.find('?')
930 content_type = self.path[query_char + 1:].strip()
931 if not content_type:
932 content_type = 'text/html'
933 self.send_response(200)
934 self.send_header('Content-Type', content_type)
935 self.end_headers()
936 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
937 return True
938
939 def ServerRedirectHandler(self):
940 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000941 '/server-redirect?http://foo.bar/asdf' to redirect to
942 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000943
944 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000945 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000946 return False
947
948 query_char = self.path.find('?')
949 if query_char < 0 or len(self.path) <= query_char + 1:
950 self.sendRedirectHelp(test_name)
951 return True
952 dest = self.path[query_char + 1:]
953
954 self.send_response(301) # moved permanently
955 self.send_header('Location', dest)
956 self.send_header('Content-type', 'text/html')
957 self.end_headers()
958 self.wfile.write('<html><head>')
959 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
960
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000961 return True
initial.commit94958cf2008-07-26 22:42:52 +0000962
963 def ClientRedirectHandler(self):
964 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000965 '/client-redirect?http://foo.bar/asdf' to redirect to
966 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000967
968 test_name = "/client-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(200)
979 self.send_header('Content-type', 'text/html')
980 self.end_headers()
981 self.wfile.write('<html><head>')
982 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
983 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
984
985 return True
986
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000987 def ChromiumSyncTimeHandler(self):
988 """Handle Chromium sync .../time requests.
989
990 The syncer sometimes checks server reachability by examining /time.
991 """
992 test_name = "/chromiumsync/time"
993 if not self._ShouldHandleRequest(test_name):
994 return False
995
996 self.send_response(200)
997 self.send_header('Content-type', 'text/html')
998 self.end_headers()
999 return True
1000
1001 def ChromiumSyncCommandHandler(self):
1002 """Handle a chromiumsync command arriving via http.
1003
1004 This covers all sync protocol commands: authentication, getupdates, and
1005 commit.
1006 """
1007 test_name = "/chromiumsync/command"
1008 if not self._ShouldHandleRequest(test_name):
1009 return False
1010
1011 length = int(self.headers.getheader('content-length'))
1012 raw_request = self.rfile.read(length)
1013
pathorn@chromium.org44920122010-07-27 18:25:35 +00001014 if not self.server._sync_handler:
1015 import chromiumsync
1016 self.server._sync_handler = chromiumsync.TestServer()
1017 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1018 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001019 self.send_response(http_response)
1020 self.end_headers()
1021 self.wfile.write(raw_reply)
1022 return True
1023
tony@chromium.org03266982010-03-05 03:18:42 +00001024 def MultipartHandler(self):
1025 """Send a multipart response (10 text/html pages)."""
1026 test_name = "/multipart"
1027 if not self._ShouldHandleRequest(test_name):
1028 return False
1029
1030 num_frames = 10
1031 bound = '12345'
1032 self.send_response(200)
1033 self.send_header('Content-type',
1034 'multipart/x-mixed-replace;boundary=' + bound)
1035 self.end_headers()
1036
1037 for i in xrange(num_frames):
1038 self.wfile.write('--' + bound + '\r\n')
1039 self.wfile.write('Content-type: text/html\r\n\r\n')
1040 self.wfile.write('<title>page ' + str(i) + '</title>')
1041 self.wfile.write('page ' + str(i))
1042
1043 self.wfile.write('--' + bound + '--')
1044 return True
1045
initial.commit94958cf2008-07-26 22:42:52 +00001046 def DefaultResponseHandler(self):
1047 """This is the catch-all response handler for requests that aren't handled
1048 by one of the special handlers above.
1049 Note that we specify the content-length as without it the https connection
1050 is not closed properly (and the browser keeps expecting data)."""
1051
1052 contents = "Default response given for path: " + self.path
1053 self.send_response(200)
1054 self.send_header('Content-type', 'text/html')
1055 self.send_header("Content-Length", len(contents))
1056 self.end_headers()
1057 self.wfile.write(contents)
1058 return True
1059
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001060 def RedirectConnectHandler(self):
1061 """Sends a redirect to the CONNECT request for www.redirect.com. This
1062 response is not specified by the RFC, so the browser should not follow
1063 the redirect."""
1064
1065 if (self.path.find("www.redirect.com") < 0):
1066 return False
1067
1068 dest = "http://www.destination.com/foo.js"
1069
1070 self.send_response(302) # moved temporarily
1071 self.send_header('Location', dest)
1072 self.send_header('Connection', 'close')
1073 self.end_headers()
1074 return True
1075
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001076 def ServerAuthConnectHandler(self):
1077 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1078 response doesn't make sense because the proxy server cannot request
1079 server authentication."""
1080
1081 if (self.path.find("www.server-auth.com") < 0):
1082 return False
1083
1084 challenge = 'Basic realm="WallyWorld"'
1085
1086 self.send_response(401) # unauthorized
1087 self.send_header('WWW-Authenticate', challenge)
1088 self.send_header('Connection', 'close')
1089 self.end_headers()
1090 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001091
1092 def DefaultConnectResponseHandler(self):
1093 """This is the catch-all response handler for CONNECT requests that aren't
1094 handled by one of the special handlers above. Real Web servers respond
1095 with 400 to CONNECT requests."""
1096
1097 contents = "Your client has issued a malformed or illegal request."
1098 self.send_response(400) # bad request
1099 self.send_header('Content-type', 'text/html')
1100 self.send_header("Content-Length", len(contents))
1101 self.end_headers()
1102 self.wfile.write(contents)
1103 return True
1104
1105 def do_CONNECT(self):
1106 for handler in self._connect_handlers:
1107 if handler():
1108 return
1109
initial.commit94958cf2008-07-26 22:42:52 +00001110 def do_GET(self):
1111 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001112 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001113 return
1114
1115 def do_POST(self):
1116 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001117 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001118 return
1119
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001120 def do_PUT(self):
1121 for handler in self._put_handlers:
1122 if handler():
1123 return
1124
initial.commit94958cf2008-07-26 22:42:52 +00001125 # called by the redirect handling function when there is no parameter
1126 def sendRedirectHelp(self, redirect_name):
1127 self.send_response(200)
1128 self.send_header('Content-type', 'text/html')
1129 self.end_headers()
1130 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1131 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1132 self.wfile.write('</body></html>')
1133
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001134def MakeDataDir():
1135 if options.data_dir:
1136 if not os.path.isdir(options.data_dir):
1137 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1138 return None
1139 my_data_dir = options.data_dir
1140 else:
1141 # Create the default path to our data dir, relative to the exe dir.
1142 my_data_dir = os.path.dirname(sys.argv[0])
1143 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001144 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001145
1146 #TODO(ibrar): Must use Find* funtion defined in google\tools
1147 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1148
1149 return my_data_dir
1150
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001151class FileMultiplexer:
1152 def __init__(self, fd1, fd2) :
1153 self.__fd1 = fd1
1154 self.__fd2 = fd2
1155
1156 def __del__(self) :
1157 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1158 self.__fd1.close()
1159 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1160 self.__fd2.close()
1161
1162 def write(self, text) :
1163 self.__fd1.write(text)
1164 self.__fd2.write(text)
1165
1166 def flush(self) :
1167 self.__fd1.flush()
1168 self.__fd2.flush()
1169
initial.commit94958cf2008-07-26 22:42:52 +00001170def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001171 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001172 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1173 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001174
1175 port = options.port
1176
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001177 if options.server_type == SERVER_HTTP:
1178 if options.cert:
1179 # let's make sure the cert file exists.
1180 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001181 print 'specified server cert file not found: ' + options.cert + \
1182 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001183 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001184 for ca_cert in options.ssl_client_ca:
1185 if not os.path.isfile(ca_cert):
1186 print 'specified trusted client CA file not found: ' + ca_cert + \
1187 ' exiting...'
1188 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001189 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001190 options.ssl_client_auth, options.ssl_client_ca)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001191 print 'HTTPS server started on port %d...' % port
1192 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001193 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001194 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001195
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001196 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001197 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001198 server._sync_handler = None
1199
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001200 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001201 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001202 my_data_dir = MakeDataDir()
1203
1204 def line_logger(msg):
1205 if (msg.find("kill") >= 0):
1206 server.stop = True
1207 print 'shutting down server'
1208 sys.exit(0)
1209
1210 # Instantiate a dummy authorizer for managing 'virtual' users
1211 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1212
1213 # Define a new user having full r/w permissions and a read-only
1214 # anonymous user
1215 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1216
1217 authorizer.add_anonymous(my_data_dir)
1218
1219 # Instantiate FTP handler class
1220 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1221 ftp_handler.authorizer = authorizer
1222 pyftpdlib.ftpserver.logline = line_logger
1223
1224 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001225 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1226 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001227
1228 # Instantiate FTP server class and listen to 127.0.0.1:port
1229 address = ('127.0.0.1', port)
1230 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1231 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001232
1233 try:
1234 server.serve_forever()
1235 except KeyboardInterrupt:
1236 print 'shutting down server'
1237 server.stop = True
1238
1239if __name__ == '__main__':
1240 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001241 option_parser.add_option("-f", '--ftp', action='store_const',
1242 const=SERVER_FTP, default=SERVER_HTTP,
1243 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001244 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001245 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001246 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001247 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001248 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001249 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001250 help='Specify that https should be used, specify '
1251 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001252 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001253 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1254 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001255 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1256 help='Specify that the client certificate request '
1257 'should indicate that it supports the CA contained '
1258 'in the specified certificate file')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001259 option_parser.add_option('', '--file-root-url', default='/files/',
1260 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001261 option_parser.add_option('', '--never-die', default=False,
1262 action="store_true",
1263 help='Prevent the server from dying when visiting '
1264 'a /kill URL. Useful for manually running some '
1265 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001266 options, args = option_parser.parse_args()
1267
1268 sys.exit(main(options, args))