blob: ee2476c1b80518798966595a7265b9ff04ea299a [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
davidben@chromium.org06fcf202010-09-22 18:15:23 +000042if sys.platform == 'win32':
43 import msvcrt
44
maruel@chromium.org756cf982009-03-05 12:46:38 +000045SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000046SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000047
48debug_output = sys.stderr
49def debug(str):
50 debug_output.write(str + "\n")
51 debug_output.flush()
52
53class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
54 """This is a specialization of of BaseHTTPServer to allow it
55 to be exited cleanly (by setting its "stop" member to True)."""
56
57 def serve_forever(self):
58 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000059 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000060 while not self.stop:
61 self.handle_request()
62 self.socket.close()
63
64class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
65 """This is a specialization of StoppableHTTPerver that add https support."""
66
davidben@chromium.org31282a12010-08-07 01:10:02 +000067 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000068 ssl_client_auth, ssl_client_cas):
initial.commit94958cf2008-07-26 22:42:52 +000069 s = open(cert_path).read()
70 x509 = tlslite.api.X509()
71 x509.parse(s)
72 self.cert_chain = tlslite.api.X509CertChain([x509])
73 s = open(cert_path).read()
74 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000075 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000076 self.ssl_client_cas = []
77 for ca_file in ssl_client_cas:
78 s = open(ca_file).read()
79 x509 = tlslite.api.X509()
80 x509.parse(s)
81 self.ssl_client_cas.append(x509.subject)
initial.commit94958cf2008-07-26 22:42:52 +000082
83 self.session_cache = tlslite.api.SessionCache()
84 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
85
86 def handshake(self, tlsConnection):
87 """Creates the SSL connection."""
88 try:
89 tlsConnection.handshakeServer(certChain=self.cert_chain,
90 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000091 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000092 reqCert=self.ssl_client_auth,
93 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +000094 tlsConnection.ignoreAbruptClose = True
95 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000096 except tlslite.api.TLSAbruptCloseError:
97 # Ignore abrupt close.
98 return True
initial.commit94958cf2008-07-26 22:42:52 +000099 except tlslite.api.TLSError, error:
100 print "Handshake failure:", str(error)
101 return False
102
103class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
104
105 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000106 self._connect_handlers = [
107 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000108 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000109 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000110 self._get_handlers = [
111 self.KillHandler,
112 self.NoCacheMaxAgeTimeHandler,
113 self.NoCacheTimeHandler,
114 self.CacheTimeHandler,
115 self.CacheExpiresHandler,
116 self.CacheProxyRevalidateHandler,
117 self.CachePrivateHandler,
118 self.CachePublicHandler,
119 self.CacheSMaxAgeHandler,
120 self.CacheMustRevalidateHandler,
121 self.CacheMustRevalidateMaxAgeHandler,
122 self.CacheNoStoreHandler,
123 self.CacheNoStoreMaxAgeHandler,
124 self.CacheNoTransformHandler,
125 self.DownloadHandler,
126 self.DownloadFinishHandler,
127 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000128 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000129 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000130 self.FileHandler,
131 self.RealFileWithCommonHeaderHandler,
132 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000133 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000134 self.AuthBasicHandler,
135 self.AuthDigestHandler,
136 self.SlowServerHandler,
137 self.ContentTypeHandler,
138 self.ServerRedirectHandler,
139 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000140 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000141 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000142 self.DefaultResponseHandler]
143 self._post_handlers = [
144 self.EchoTitleHandler,
145 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000146 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000147 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000148 self._put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000149 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 = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000154 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000155 'gif': 'image/gif',
156 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000157 'jpg' : 'image/jpeg',
158 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000159 }
initial.commit94958cf2008-07-26 22:42:52 +0000160 self._default_mime_type = 'text/html'
161
maruel@google.come250a9b2009-03-10 17:39:46 +0000162 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
163 client_address,
164 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000165
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000166 def log_request(self, *args, **kwargs):
167 # Disable request logging to declutter test log output.
168 pass
169
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000170 def _ShouldHandleRequest(self, handler_name):
171 """Determines if the path can be handled by the handler.
172
173 We consider a handler valid if the path begins with the
174 handler name. It can optionally be followed by "?*", "/*".
175 """
176
177 pattern = re.compile('%s($|\?|/).*' % handler_name)
178 return pattern.match(self.path)
179
initial.commit94958cf2008-07-26 22:42:52 +0000180 def GetMIMETypeFromName(self, file_name):
181 """Returns the mime type for the specified file_name. So far it only looks
182 at the file extension."""
183
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000184 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000185 if len(extension) == 0:
186 # no extension.
187 return self._default_mime_type
188
ericroman@google.comc17ca532009-05-07 03:51:05 +0000189 # extension starts with a dot, so we need to remove it
190 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000191
192 def KillHandler(self):
193 """This request handler kills the server, for use when we're done"
194 with the a particular test."""
195
196 if (self.path.find("kill") < 0):
197 return False
198
199 self.send_response(200)
200 self.send_header('Content-type', 'text/html')
201 self.send_header('Cache-Control', 'max-age=0')
202 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000203 if options.never_die:
204 self.wfile.write('I cannot die!! BWAHAHA')
205 else:
206 self.wfile.write('Goodbye cruel world!')
207 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000208
209 return True
210
211 def NoCacheMaxAgeTimeHandler(self):
212 """This request handler yields a page with the title set to the current
213 system time, and no caching requested."""
214
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000215 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000216 return False
217
218 self.send_response(200)
219 self.send_header('Cache-Control', 'max-age=0')
220 self.send_header('Content-type', 'text/html')
221 self.end_headers()
222
maruel@google.come250a9b2009-03-10 17:39:46 +0000223 self.wfile.write('<html><head><title>%s</title></head></html>' %
224 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000225
226 return True
227
228 def NoCacheTimeHandler(self):
229 """This request handler yields a page with the title set to the current
230 system time, and no caching requested."""
231
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000232 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000233 return False
234
235 self.send_response(200)
236 self.send_header('Cache-Control', 'no-cache')
237 self.send_header('Content-type', 'text/html')
238 self.end_headers()
239
maruel@google.come250a9b2009-03-10 17:39:46 +0000240 self.wfile.write('<html><head><title>%s</title></head></html>' %
241 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000242
243 return True
244
245 def CacheTimeHandler(self):
246 """This request handler yields a page with the title set to the current
247 system time, and allows caching for one minute."""
248
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000249 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000250 return False
251
252 self.send_response(200)
253 self.send_header('Cache-Control', 'max-age=60')
254 self.send_header('Content-type', 'text/html')
255 self.end_headers()
256
maruel@google.come250a9b2009-03-10 17:39:46 +0000257 self.wfile.write('<html><head><title>%s</title></head></html>' %
258 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000259
260 return True
261
262 def CacheExpiresHandler(self):
263 """This request handler yields a page with the title set to the current
264 system time, and set the page to expire on 1 Jan 2099."""
265
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000266 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000267 return False
268
269 self.send_response(200)
270 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
271 self.send_header('Content-type', 'text/html')
272 self.end_headers()
273
maruel@google.come250a9b2009-03-10 17:39:46 +0000274 self.wfile.write('<html><head><title>%s</title></head></html>' %
275 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000276
277 return True
278
279 def CacheProxyRevalidateHandler(self):
280 """This request handler yields a page with the title set to the current
281 system time, and allows caching for 60 seconds"""
282
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000283 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000284 return False
285
286 self.send_response(200)
287 self.send_header('Content-type', 'text/html')
288 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
289 self.end_headers()
290
maruel@google.come250a9b2009-03-10 17:39:46 +0000291 self.wfile.write('<html><head><title>%s</title></head></html>' %
292 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000293
294 return True
295
296 def CachePrivateHandler(self):
297 """This request handler yields a page with the title set to the current
298 system time, and allows caching for 5 seconds."""
299
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000300 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000301 return False
302
303 self.send_response(200)
304 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000305 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000306 self.end_headers()
307
maruel@google.come250a9b2009-03-10 17:39:46 +0000308 self.wfile.write('<html><head><title>%s</title></head></html>' %
309 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000310
311 return True
312
313 def CachePublicHandler(self):
314 """This request handler yields a page with the title set to the current
315 system time, and allows caching for 5 seconds."""
316
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000317 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000318 return False
319
320 self.send_response(200)
321 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000322 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000323 self.end_headers()
324
maruel@google.come250a9b2009-03-10 17:39:46 +0000325 self.wfile.write('<html><head><title>%s</title></head></html>' %
326 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000327
328 return True
329
330 def CacheSMaxAgeHandler(self):
331 """This request handler yields a page with the title set to the current
332 system time, and does not allow for caching."""
333
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000334 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000335 return False
336
337 self.send_response(200)
338 self.send_header('Content-type', 'text/html')
339 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
340 self.end_headers()
341
maruel@google.come250a9b2009-03-10 17:39:46 +0000342 self.wfile.write('<html><head><title>%s</title></head></html>' %
343 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000344
345 return True
346
347 def CacheMustRevalidateHandler(self):
348 """This request handler yields a page with the title set to the current
349 system time, and does not allow caching."""
350
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000351 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000352 return False
353
354 self.send_response(200)
355 self.send_header('Content-type', 'text/html')
356 self.send_header('Cache-Control', 'must-revalidate')
357 self.end_headers()
358
maruel@google.come250a9b2009-03-10 17:39:46 +0000359 self.wfile.write('<html><head><title>%s</title></head></html>' %
360 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000361
362 return True
363
364 def CacheMustRevalidateMaxAgeHandler(self):
365 """This request handler yields a page with the title set to the current
366 system time, and does not allow caching event though max-age of 60
367 seconds is specified."""
368
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000369 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000370 return False
371
372 self.send_response(200)
373 self.send_header('Content-type', 'text/html')
374 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
375 self.end_headers()
376
maruel@google.come250a9b2009-03-10 17:39:46 +0000377 self.wfile.write('<html><head><title>%s</title></head></html>' %
378 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000379
380 return True
381
initial.commit94958cf2008-07-26 22:42:52 +0000382 def CacheNoStoreHandler(self):
383 """This request handler yields a page with the title set to the current
384 system time, and does not allow the page to be stored."""
385
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000386 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000387 return False
388
389 self.send_response(200)
390 self.send_header('Content-type', 'text/html')
391 self.send_header('Cache-Control', 'no-store')
392 self.end_headers()
393
maruel@google.come250a9b2009-03-10 17:39:46 +0000394 self.wfile.write('<html><head><title>%s</title></head></html>' %
395 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000396
397 return True
398
399 def CacheNoStoreMaxAgeHandler(self):
400 """This request handler yields a page with the title set to the current
401 system time, and does not allow the page to be stored even though max-age
402 of 60 seconds is specified."""
403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
408 self.send_header('Content-type', 'text/html')
409 self.send_header('Cache-Control', 'max-age=60, no-store')
410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417
418 def CacheNoTransformHandler(self):
419 """This request handler yields a page with the title set to the current
420 system time, and does not allow the content to transformed during
421 user-agent caching"""
422
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000423 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000424 return False
425
426 self.send_response(200)
427 self.send_header('Content-type', 'text/html')
428 self.send_header('Cache-Control', 'no-transform')
429 self.end_headers()
430
maruel@google.come250a9b2009-03-10 17:39:46 +0000431 self.wfile.write('<html><head><title>%s</title></head></html>' %
432 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000433
434 return True
435
436 def EchoHeader(self):
437 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000438 """The only difference between this function and the EchoHeaderOverride"""
439 """function is in the parameter being passed to the helper function"""
440 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000441
ananta@chromium.org219b2062009-10-23 16:09:41 +0000442 def EchoHeaderOverride(self):
443 """This handler echoes back the value of a specific request header."""
444 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
445 """IE to issue HTTP requests using the host network stack."""
446 """The Accept and Charset tests which expect the server to echo back"""
447 """the corresponding headers fail here as IE returns cached responses"""
448 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
449 """treats this request as a new request and does not cache it."""
450 return self.EchoHeaderHelper("/echoheaderoverride")
451
452 def EchoHeaderHelper(self, echo_header):
453 """This function echoes back the value of the request header passed in."""
454 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000455 return False
456
457 query_char = self.path.find('?')
458 if query_char != -1:
459 header_name = self.path[query_char+1:]
460
461 self.send_response(200)
462 self.send_header('Content-type', 'text/plain')
463 self.send_header('Cache-control', 'max-age=60000')
464 # insert a vary header to properly indicate that the cachability of this
465 # request is subject to value of the request header being echoed.
466 if len(header_name) > 0:
467 self.send_header('Vary', header_name)
468 self.end_headers()
469
470 if len(header_name) > 0:
471 self.wfile.write(self.headers.getheader(header_name))
472
473 return True
474
475 def EchoHandler(self):
476 """This handler just echoes back the payload of the request, for testing
477 form submission."""
478
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000480 return False
481
482 self.send_response(200)
483 self.send_header('Content-type', 'text/html')
484 self.end_headers()
485 length = int(self.headers.getheader('content-length'))
486 request = self.rfile.read(length)
487 self.wfile.write(request)
488 return True
489
490 def EchoTitleHandler(self):
491 """This handler is like Echo, but sets the page title to the request."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
497 self.send_header('Content-type', 'text/html')
498 self.end_headers()
499 length = int(self.headers.getheader('content-length'))
500 request = self.rfile.read(length)
501 self.wfile.write('<html><head><title>')
502 self.wfile.write(request)
503 self.wfile.write('</title></head></html>')
504 return True
505
506 def EchoAllHandler(self):
507 """This handler yields a (more) human-readable page listing information
508 about the request header & contents."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
514 self.send_header('Content-type', 'text/html')
515 self.end_headers()
516 self.wfile.write('<html><head><style>'
517 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
518 '</style></head><body>'
519 '<div style="float: right">'
520 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
521 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000522
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000523 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000524 length = int(self.headers.getheader('content-length'))
525 qs = self.rfile.read(length)
526 params = cgi.parse_qs(qs, keep_blank_values=1)
527
528 for param in params:
529 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000530
531 self.wfile.write('</pre>')
532
533 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
534
535 self.wfile.write('</body></html>')
536 return True
537
538 def DownloadHandler(self):
539 """This handler sends a downloadable file with or without reporting
540 the size (6K)."""
541
542 if self.path.startswith("/download-unknown-size"):
543 send_length = False
544 elif self.path.startswith("/download-known-size"):
545 send_length = True
546 else:
547 return False
548
549 #
550 # The test which uses this functionality is attempting to send
551 # small chunks of data to the client. Use a fairly large buffer
552 # so that we'll fill chrome's IO buffer enough to force it to
553 # actually write the data.
554 # See also the comments in the client-side of this test in
555 # download_uitest.cc
556 #
557 size_chunk1 = 35*1024
558 size_chunk2 = 10*1024
559
560 self.send_response(200)
561 self.send_header('Content-type', 'application/octet-stream')
562 self.send_header('Cache-Control', 'max-age=0')
563 if send_length:
564 self.send_header('Content-Length', size_chunk1 + size_chunk2)
565 self.end_headers()
566
567 # First chunk of data:
568 self.wfile.write("*" * size_chunk1)
569 self.wfile.flush()
570
571 # handle requests until one of them clears this flag.
572 self.server.waitForDownload = True
573 while self.server.waitForDownload:
574 self.server.handle_request()
575
576 # Second chunk of data:
577 self.wfile.write("*" * size_chunk2)
578 return True
579
580 def DownloadFinishHandler(self):
581 """This handler just tells the server to finish the current download."""
582
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000583 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000584 return False
585
586 self.server.waitForDownload = False
587 self.send_response(200)
588 self.send_header('Content-type', 'text/html')
589 self.send_header('Cache-Control', 'max-age=0')
590 self.end_headers()
591 return True
592
593 def FileHandler(self):
594 """This handler sends the contents of the requested file. Wow, it's like
595 a real webserver!"""
596
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000597 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000598 if not self.path.startswith(prefix):
599 return False
600
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000601 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000602 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000603 self.rfile.read(int(self.headers.getheader('content-length')))
604
initial.commit94958cf2008-07-26 22:42:52 +0000605 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000606 if file.find('?') > -1:
607 # Ignore the query parameters entirely.
608 url, querystring = file.split('?')
609 else:
610 url = file
611 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000612 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000613 if os.path.isdir(path):
614 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000615
616 if not os.path.isfile(path):
617 print "File not found " + file + " full path:" + path
618 self.send_error(404)
619 return True
620
621 f = open(path, "rb")
622 data = f.read()
623 f.close()
624
625 # If file.mock-http-headers exists, it contains the headers we
626 # should send. Read them in and parse them.
627 headers_path = path + '.mock-http-headers'
628 if os.path.isfile(headers_path):
629 f = open(headers_path, "r")
630
631 # "HTTP/1.1 200 OK"
632 response = f.readline()
633 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
634 self.send_response(int(status_code))
635
636 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000637 header_values = re.findall('(\S+):\s*(.*)', line)
638 if len(header_values) > 0:
639 # "name: value"
640 name, value = header_values[0]
641 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000642 f.close()
643 else:
644 # Could be more generic once we support mime-type sniffing, but for
645 # now we need to set it explicitly.
646 self.send_response(200)
647 self.send_header('Content-type', self.GetMIMETypeFromName(file))
648 self.send_header('Content-Length', len(data))
649 self.end_headers()
650
651 self.wfile.write(data)
652
653 return True
654
655 def RealFileWithCommonHeaderHandler(self):
656 """This handler sends the contents of the requested file without the pseudo
657 http head!"""
658
659 prefix='/realfiles/'
660 if not self.path.startswith(prefix):
661 return False
662
663 file = self.path[len(prefix):]
664 path = os.path.join(self.server.data_dir, file)
665
666 try:
667 f = open(path, "rb")
668 data = f.read()
669 f.close()
670
671 # just simply set the MIME as octal stream
672 self.send_response(200)
673 self.send_header('Content-type', 'application/octet-stream')
674 self.end_headers()
675
676 self.wfile.write(data)
677 except:
678 self.send_error(404)
679
680 return True
681
682 def RealBZ2FileWithCommonHeaderHandler(self):
683 """This handler sends the bzip2 contents of the requested file with
684 corresponding Content-Encoding field in http head!"""
685
686 prefix='/realbz2files/'
687 if not self.path.startswith(prefix):
688 return False
689
690 parts = self.path.split('?')
691 file = parts[0][len(prefix):]
692 path = os.path.join(self.server.data_dir, file) + '.bz2'
693
694 if len(parts) > 1:
695 options = parts[1]
696 else:
697 options = ''
698
699 try:
700 self.send_response(200)
701 accept_encoding = self.headers.get("Accept-Encoding")
702 if accept_encoding.find("bzip2") != -1:
703 f = open(path, "rb")
704 data = f.read()
705 f.close()
706 self.send_header('Content-Encoding', 'bzip2')
707 self.send_header('Content-type', 'application/x-bzip2')
708 self.end_headers()
709 if options == 'incremental-header':
710 self.wfile.write(data[:1])
711 self.wfile.flush()
712 time.sleep(1.0)
713 self.wfile.write(data[1:])
714 else:
715 self.wfile.write(data)
716 else:
717 """client do not support bzip2 format, send pseudo content
718 """
719 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
720 self.end_headers()
721 self.wfile.write("you do not support bzip2 encoding")
722 except:
723 self.send_error(404)
724
725 return True
726
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000727 def SetCookieHandler(self):
728 """This handler just sets a cookie, for testing cookie handling."""
729
730 if not self._ShouldHandleRequest("/set-cookie"):
731 return False
732
733 query_char = self.path.find('?')
734 if query_char != -1:
735 cookie_values = self.path[query_char + 1:].split('&')
736 else:
737 cookie_values = ("",)
738 self.send_response(200)
739 self.send_header('Content-type', 'text/html')
740 for cookie_value in cookie_values:
741 self.send_header('Set-Cookie', '%s' % cookie_value)
742 self.end_headers()
743 for cookie_value in cookie_values:
744 self.wfile.write('%s' % cookie_value)
745 return True
746
initial.commit94958cf2008-07-26 22:42:52 +0000747 def AuthBasicHandler(self):
748 """This handler tests 'Basic' authentication. It just sends a page with
749 title 'user/pass' if you succeed."""
750
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000751 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000752 return False
753
754 username = userpass = password = b64str = ""
755
ericroman@google.com239b4d82009-03-27 04:00:22 +0000756 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
757
initial.commit94958cf2008-07-26 22:42:52 +0000758 auth = self.headers.getheader('authorization')
759 try:
760 if not auth:
761 raise Exception('no auth')
762 b64str = re.findall(r'Basic (\S+)', auth)[0]
763 userpass = base64.b64decode(b64str)
764 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
765 if password != 'secret':
766 raise Exception('wrong password')
767 except Exception, e:
768 # Authentication failed.
769 self.send_response(401)
770 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
771 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000772 if set_cookie_if_challenged:
773 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000774 self.end_headers()
775 self.wfile.write('<html><head>')
776 self.wfile.write('<title>Denied: %s</title>' % e)
777 self.wfile.write('</head><body>')
778 self.wfile.write('auth=%s<p>' % auth)
779 self.wfile.write('b64str=%s<p>' % b64str)
780 self.wfile.write('username: %s<p>' % username)
781 self.wfile.write('userpass: %s<p>' % userpass)
782 self.wfile.write('password: %s<p>' % password)
783 self.wfile.write('You sent:<br>%s<p>' % self.headers)
784 self.wfile.write('</body></html>')
785 return True
786
787 # Authentication successful. (Return a cachable response to allow for
788 # testing cached pages that require authentication.)
789 if_none_match = self.headers.getheader('if-none-match')
790 if if_none_match == "abc":
791 self.send_response(304)
792 self.end_headers()
793 else:
794 self.send_response(200)
795 self.send_header('Content-type', 'text/html')
796 self.send_header('Cache-control', 'max-age=60000')
797 self.send_header('Etag', 'abc')
798 self.end_headers()
799 self.wfile.write('<html><head>')
800 self.wfile.write('<title>%s/%s</title>' % (username, password))
801 self.wfile.write('</head><body>')
802 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000803 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000804 self.wfile.write('</body></html>')
805
806 return True
807
tonyg@chromium.org75054202010-03-31 22:06:10 +0000808 def GetNonce(self, force_reset=False):
809 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000810
tonyg@chromium.org75054202010-03-31 22:06:10 +0000811 This is a fake implementation. A real implementation would only use a given
812 nonce a single time (hence the name n-once). However, for the purposes of
813 unittesting, we don't care about the security of the nonce.
814
815 Args:
816 force_reset: Iff set, the nonce will be changed. Useful for testing the
817 "stale" response.
818 """
819 if force_reset or not self.server.nonce_time:
820 self.server.nonce_time = time.time()
821 return _new_md5('privatekey%s%d' %
822 (self.path, self.server.nonce_time)).hexdigest()
823
824 def AuthDigestHandler(self):
825 """This handler tests 'Digest' authentication.
826
827 It just sends a page with title 'user/pass' if you succeed.
828
829 A stale response is sent iff "stale" is present in the request path.
830 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000831 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000832 return False
833
tonyg@chromium.org75054202010-03-31 22:06:10 +0000834 stale = 'stale' in self.path
835 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000836 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000837 password = 'secret'
838 realm = 'testrealm'
839
840 auth = self.headers.getheader('authorization')
841 pairs = {}
842 try:
843 if not auth:
844 raise Exception('no auth')
845 if not auth.startswith('Digest'):
846 raise Exception('not digest')
847 # Pull out all the name="value" pairs as a dictionary.
848 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
849
850 # Make sure it's all valid.
851 if pairs['nonce'] != nonce:
852 raise Exception('wrong nonce')
853 if pairs['opaque'] != opaque:
854 raise Exception('wrong opaque')
855
856 # Check the 'response' value and make sure it matches our magic hash.
857 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000858 hash_a1 = _new_md5(
859 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000860 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000861 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000862 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000863 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
864 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000865 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000866
867 if pairs['response'] != response:
868 raise Exception('wrong password')
869 except Exception, e:
870 # Authentication failed.
871 self.send_response(401)
872 hdr = ('Digest '
873 'realm="%s", '
874 'domain="/", '
875 'qop="auth", '
876 'algorithm=MD5, '
877 'nonce="%s", '
878 'opaque="%s"') % (realm, nonce, opaque)
879 if stale:
880 hdr += ', stale="TRUE"'
881 self.send_header('WWW-Authenticate', hdr)
882 self.send_header('Content-type', 'text/html')
883 self.end_headers()
884 self.wfile.write('<html><head>')
885 self.wfile.write('<title>Denied: %s</title>' % e)
886 self.wfile.write('</head><body>')
887 self.wfile.write('auth=%s<p>' % auth)
888 self.wfile.write('pairs=%s<p>' % pairs)
889 self.wfile.write('You sent:<br>%s<p>' % self.headers)
890 self.wfile.write('We are replying:<br>%s<p>' % hdr)
891 self.wfile.write('</body></html>')
892 return True
893
894 # Authentication successful.
895 self.send_response(200)
896 self.send_header('Content-type', 'text/html')
897 self.end_headers()
898 self.wfile.write('<html><head>')
899 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
900 self.wfile.write('</head><body>')
901 self.wfile.write('auth=%s<p>' % auth)
902 self.wfile.write('pairs=%s<p>' % pairs)
903 self.wfile.write('</body></html>')
904
905 return True
906
907 def SlowServerHandler(self):
908 """Wait for the user suggested time before responding. The syntax is
909 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000910 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000911 return False
912 query_char = self.path.find('?')
913 wait_sec = 1.0
914 if query_char >= 0:
915 try:
916 wait_sec = int(self.path[query_char + 1:])
917 except ValueError:
918 pass
919 time.sleep(wait_sec)
920 self.send_response(200)
921 self.send_header('Content-type', 'text/plain')
922 self.end_headers()
923 self.wfile.write("waited %d seconds" % wait_sec)
924 return True
925
926 def ContentTypeHandler(self):
927 """Returns a string of html with the given content type. E.g.,
928 /contenttype?text/css returns an html file with the Content-Type
929 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000930 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000931 return False
932 query_char = self.path.find('?')
933 content_type = self.path[query_char + 1:].strip()
934 if not content_type:
935 content_type = 'text/html'
936 self.send_response(200)
937 self.send_header('Content-Type', content_type)
938 self.end_headers()
939 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
940 return True
941
942 def ServerRedirectHandler(self):
943 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000944 '/server-redirect?http://foo.bar/asdf' to redirect to
945 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000946
947 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000948 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000949 return False
950
951 query_char = self.path.find('?')
952 if query_char < 0 or len(self.path) <= query_char + 1:
953 self.sendRedirectHelp(test_name)
954 return True
955 dest = self.path[query_char + 1:]
956
957 self.send_response(301) # moved permanently
958 self.send_header('Location', dest)
959 self.send_header('Content-type', 'text/html')
960 self.end_headers()
961 self.wfile.write('<html><head>')
962 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
963
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000964 return True
initial.commit94958cf2008-07-26 22:42:52 +0000965
966 def ClientRedirectHandler(self):
967 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000968 '/client-redirect?http://foo.bar/asdf' to redirect to
969 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000970
971 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000972 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000973 return False
974
975 query_char = self.path.find('?');
976 if query_char < 0 or len(self.path) <= query_char + 1:
977 self.sendRedirectHelp(test_name)
978 return True
979 dest = self.path[query_char + 1:]
980
981 self.send_response(200)
982 self.send_header('Content-type', 'text/html')
983 self.end_headers()
984 self.wfile.write('<html><head>')
985 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
986 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
987
988 return True
989
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000990 def ChromiumSyncTimeHandler(self):
991 """Handle Chromium sync .../time requests.
992
993 The syncer sometimes checks server reachability by examining /time.
994 """
995 test_name = "/chromiumsync/time"
996 if not self._ShouldHandleRequest(test_name):
997 return False
998
999 self.send_response(200)
1000 self.send_header('Content-type', 'text/html')
1001 self.end_headers()
1002 return True
1003
1004 def ChromiumSyncCommandHandler(self):
1005 """Handle a chromiumsync command arriving via http.
1006
1007 This covers all sync protocol commands: authentication, getupdates, and
1008 commit.
1009 """
1010 test_name = "/chromiumsync/command"
1011 if not self._ShouldHandleRequest(test_name):
1012 return False
1013
1014 length = int(self.headers.getheader('content-length'))
1015 raw_request = self.rfile.read(length)
1016
pathorn@chromium.org44920122010-07-27 18:25:35 +00001017 if not self.server._sync_handler:
1018 import chromiumsync
1019 self.server._sync_handler = chromiumsync.TestServer()
1020 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1021 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001022 self.send_response(http_response)
1023 self.end_headers()
1024 self.wfile.write(raw_reply)
1025 return True
1026
tony@chromium.org03266982010-03-05 03:18:42 +00001027 def MultipartHandler(self):
1028 """Send a multipart response (10 text/html pages)."""
1029 test_name = "/multipart"
1030 if not self._ShouldHandleRequest(test_name):
1031 return False
1032
1033 num_frames = 10
1034 bound = '12345'
1035 self.send_response(200)
1036 self.send_header('Content-type',
1037 'multipart/x-mixed-replace;boundary=' + bound)
1038 self.end_headers()
1039
1040 for i in xrange(num_frames):
1041 self.wfile.write('--' + bound + '\r\n')
1042 self.wfile.write('Content-type: text/html\r\n\r\n')
1043 self.wfile.write('<title>page ' + str(i) + '</title>')
1044 self.wfile.write('page ' + str(i))
1045
1046 self.wfile.write('--' + bound + '--')
1047 return True
1048
initial.commit94958cf2008-07-26 22:42:52 +00001049 def DefaultResponseHandler(self):
1050 """This is the catch-all response handler for requests that aren't handled
1051 by one of the special handlers above.
1052 Note that we specify the content-length as without it the https connection
1053 is not closed properly (and the browser keeps expecting data)."""
1054
1055 contents = "Default response given for path: " + self.path
1056 self.send_response(200)
1057 self.send_header('Content-type', 'text/html')
1058 self.send_header("Content-Length", len(contents))
1059 self.end_headers()
1060 self.wfile.write(contents)
1061 return True
1062
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001063 def RedirectConnectHandler(self):
1064 """Sends a redirect to the CONNECT request for www.redirect.com. This
1065 response is not specified by the RFC, so the browser should not follow
1066 the redirect."""
1067
1068 if (self.path.find("www.redirect.com") < 0):
1069 return False
1070
1071 dest = "http://www.destination.com/foo.js"
1072
1073 self.send_response(302) # moved temporarily
1074 self.send_header('Location', dest)
1075 self.send_header('Connection', 'close')
1076 self.end_headers()
1077 return True
1078
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001079 def ServerAuthConnectHandler(self):
1080 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1081 response doesn't make sense because the proxy server cannot request
1082 server authentication."""
1083
1084 if (self.path.find("www.server-auth.com") < 0):
1085 return False
1086
1087 challenge = 'Basic realm="WallyWorld"'
1088
1089 self.send_response(401) # unauthorized
1090 self.send_header('WWW-Authenticate', challenge)
1091 self.send_header('Connection', 'close')
1092 self.end_headers()
1093 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001094
1095 def DefaultConnectResponseHandler(self):
1096 """This is the catch-all response handler for CONNECT requests that aren't
1097 handled by one of the special handlers above. Real Web servers respond
1098 with 400 to CONNECT requests."""
1099
1100 contents = "Your client has issued a malformed or illegal request."
1101 self.send_response(400) # bad request
1102 self.send_header('Content-type', 'text/html')
1103 self.send_header("Content-Length", len(contents))
1104 self.end_headers()
1105 self.wfile.write(contents)
1106 return True
1107
1108 def do_CONNECT(self):
1109 for handler in self._connect_handlers:
1110 if handler():
1111 return
1112
initial.commit94958cf2008-07-26 22:42:52 +00001113 def do_GET(self):
1114 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001115 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001116 return
1117
1118 def do_POST(self):
1119 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001120 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001121 return
1122
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001123 def do_PUT(self):
1124 for handler in self._put_handlers:
1125 if handler():
1126 return
1127
initial.commit94958cf2008-07-26 22:42:52 +00001128 # called by the redirect handling function when there is no parameter
1129 def sendRedirectHelp(self, redirect_name):
1130 self.send_response(200)
1131 self.send_header('Content-type', 'text/html')
1132 self.end_headers()
1133 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1134 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1135 self.wfile.write('</body></html>')
1136
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001137def MakeDataDir():
1138 if options.data_dir:
1139 if not os.path.isdir(options.data_dir):
1140 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1141 return None
1142 my_data_dir = options.data_dir
1143 else:
1144 # Create the default path to our data dir, relative to the exe dir.
1145 my_data_dir = os.path.dirname(sys.argv[0])
1146 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001147 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001148
1149 #TODO(ibrar): Must use Find* funtion defined in google\tools
1150 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1151
1152 return my_data_dir
1153
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001154class FileMultiplexer:
1155 def __init__(self, fd1, fd2) :
1156 self.__fd1 = fd1
1157 self.__fd2 = fd2
1158
1159 def __del__(self) :
1160 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1161 self.__fd1.close()
1162 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1163 self.__fd2.close()
1164
1165 def write(self, text) :
1166 self.__fd1.write(text)
1167 self.__fd2.write(text)
1168
1169 def flush(self) :
1170 self.__fd1.flush()
1171 self.__fd2.flush()
1172
initial.commit94958cf2008-07-26 22:42:52 +00001173def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001174 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001175 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1176 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001177
1178 port = options.port
1179
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001180 if options.server_type == SERVER_HTTP:
1181 if options.cert:
1182 # let's make sure the cert file exists.
1183 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001184 print 'specified server cert file not found: ' + options.cert + \
1185 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001186 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001187 for ca_cert in options.ssl_client_ca:
1188 if not os.path.isfile(ca_cert):
1189 print 'specified trusted client CA file not found: ' + ca_cert + \
1190 ' exiting...'
1191 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001192 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001193 options.ssl_client_auth, options.ssl_client_ca)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001194 print 'HTTPS server started on port %d...' % port
1195 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001196 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001197 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001198
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001199 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001200 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001201 server._sync_handler = None
1202
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001203 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001204 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001205 my_data_dir = MakeDataDir()
1206
1207 def line_logger(msg):
1208 if (msg.find("kill") >= 0):
1209 server.stop = True
1210 print 'shutting down server'
1211 sys.exit(0)
1212
1213 # Instantiate a dummy authorizer for managing 'virtual' users
1214 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1215
1216 # Define a new user having full r/w permissions and a read-only
1217 # anonymous user
1218 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1219
1220 authorizer.add_anonymous(my_data_dir)
1221
1222 # Instantiate FTP handler class
1223 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1224 ftp_handler.authorizer = authorizer
1225 pyftpdlib.ftpserver.logline = line_logger
1226
1227 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001228 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1229 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001230
1231 # Instantiate FTP server class and listen to 127.0.0.1:port
1232 address = ('127.0.0.1', port)
1233 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1234 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001235
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001236 # Notify the parent that we've started. (BaseServer subclasses
1237 # bind their sockets on construction.)
1238 if options.startup_pipe is not None:
1239 if sys.platform == 'win32':
1240 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1241 else:
1242 fd = options.startup_pipe
1243 startup_pipe = os.fdopen(fd, "w")
1244 startup_pipe.write("READY")
1245 startup_pipe.close()
1246
initial.commit94958cf2008-07-26 22:42:52 +00001247 try:
1248 server.serve_forever()
1249 except KeyboardInterrupt:
1250 print 'shutting down server'
1251 server.stop = True
1252
1253if __name__ == '__main__':
1254 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001255 option_parser.add_option("-f", '--ftp', action='store_const',
1256 const=SERVER_FTP, default=SERVER_HTTP,
1257 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001258 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001259 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001260 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001261 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001262 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001263 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001264 help='Specify that https should be used, specify '
1265 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001266 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001267 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1268 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001269 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1270 help='Specify that the client certificate request '
1271 'should indicate that it supports the CA contained '
1272 'in the specified certificate file')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001273 option_parser.add_option('', '--file-root-url', default='/files/',
1274 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001275 option_parser.add_option('', '--never-die', default=False,
1276 action="store_true",
1277 help='Prevent the server from dying when visiting '
1278 'a /kill URL. Useful for manually running some '
1279 'tests.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001280 option_parser.add_option('', '--startup-pipe', type='int',
1281 dest='startup_pipe',
1282 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001283 options, args = option_parser.parse_args()
1284
1285 sys.exit(main(options, args))