blob: 0e1445b34b46b327ef0c42a0cfc8b09826cf3a63 [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.
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
16import base64
17import BaseHTTPServer
18import cgi
initial.commit94958cf2008-07-26 22:42:52 +000019import optparse
20import os
21import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000022import shutil
initial.commit94958cf2008-07-26 22:42:52 +000023import SocketServer
24import sys
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +000025import struct
initial.commit94958cf2008-07-26 22:42:52 +000026import time
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000027import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000028import warnings
29
30# Ignore deprecation warnings, they make our output more cluttered.
31warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000032
33import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000034import tlslite
35import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000036
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000037try:
38 import hashlib
39 _new_md5 = hashlib.md5
40except ImportError:
41 import md5
42 _new_md5 = md5.new
43
davidben@chromium.org06fcf202010-09-22 18:15:23 +000044if sys.platform == 'win32':
45 import msvcrt
46
maruel@chromium.org756cf982009-03-05 12:46:38 +000047SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000048SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000049
50debug_output = sys.stderr
51def debug(str):
52 debug_output.write(str + "\n")
53 debug_output.flush()
54
55class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
56 """This is a specialization of of BaseHTTPServer to allow it
57 to be exited cleanly (by setting its "stop" member to True)."""
58
59 def serve_forever(self):
60 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000061 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000062 while not self.stop:
63 self.handle_request()
64 self.socket.close()
65
66class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
67 """This is a specialization of StoppableHTTPerver that add https support."""
68
davidben@chromium.org31282a12010-08-07 01:10:02 +000069 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000070 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000071 s = open(cert_path).read()
72 x509 = tlslite.api.X509()
73 x509.parse(s)
74 self.cert_chain = tlslite.api.X509CertChain([x509])
75 s = open(cert_path).read()
76 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000077 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000078 self.ssl_client_cas = []
79 for ca_file in ssl_client_cas:
80 s = open(ca_file).read()
81 x509 = tlslite.api.X509()
82 x509.parse(s)
83 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000084 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
85 if ssl_bulk_ciphers is not None:
86 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000087
88 self.session_cache = tlslite.api.SessionCache()
89 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
90
91 def handshake(self, tlsConnection):
92 """Creates the SSL connection."""
93 try:
94 tlsConnection.handshakeServer(certChain=self.cert_chain,
95 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000096 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000097 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000098 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000099 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000100 tlsConnection.ignoreAbruptClose = True
101 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000102 except tlslite.api.TLSAbruptCloseError:
103 # Ignore abrupt close.
104 return True
initial.commit94958cf2008-07-26 22:42:52 +0000105 except tlslite.api.TLSError, error:
106 print "Handshake failure:", str(error)
107 return False
108
109class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
110
111 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000112 self._connect_handlers = [
113 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000114 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000115 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000116 self._get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000117 self.NoCacheMaxAgeTimeHandler,
118 self.NoCacheTimeHandler,
119 self.CacheTimeHandler,
120 self.CacheExpiresHandler,
121 self.CacheProxyRevalidateHandler,
122 self.CachePrivateHandler,
123 self.CachePublicHandler,
124 self.CacheSMaxAgeHandler,
125 self.CacheMustRevalidateHandler,
126 self.CacheMustRevalidateMaxAgeHandler,
127 self.CacheNoStoreHandler,
128 self.CacheNoStoreMaxAgeHandler,
129 self.CacheNoTransformHandler,
130 self.DownloadHandler,
131 self.DownloadFinishHandler,
132 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000133 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000134 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000135 self.FileHandler,
136 self.RealFileWithCommonHeaderHandler,
137 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000138 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000139 self.AuthBasicHandler,
140 self.AuthDigestHandler,
141 self.SlowServerHandler,
142 self.ContentTypeHandler,
143 self.ServerRedirectHandler,
144 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000145 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000146 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000147 self.DefaultResponseHandler]
148 self._post_handlers = [
149 self.EchoTitleHandler,
150 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000151 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000152 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000153 self._put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000154 self.EchoTitleHandler,
155 self.EchoAllHandler,
156 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000157
maruel@google.come250a9b2009-03-10 17:39:46 +0000158 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000159 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000160 'gif': 'image/gif',
161 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000162 'jpg' : 'image/jpeg',
163 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000164 }
initial.commit94958cf2008-07-26 22:42:52 +0000165 self._default_mime_type = 'text/html'
166
maruel@google.come250a9b2009-03-10 17:39:46 +0000167 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
168 client_address,
169 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000170
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000171 def log_request(self, *args, **kwargs):
172 # Disable request logging to declutter test log output.
173 pass
174
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000175 def _ShouldHandleRequest(self, handler_name):
176 """Determines if the path can be handled by the handler.
177
178 We consider a handler valid if the path begins with the
179 handler name. It can optionally be followed by "?*", "/*".
180 """
181
182 pattern = re.compile('%s($|\?|/).*' % handler_name)
183 return pattern.match(self.path)
184
initial.commit94958cf2008-07-26 22:42:52 +0000185 def GetMIMETypeFromName(self, file_name):
186 """Returns the mime type for the specified file_name. So far it only looks
187 at the file extension."""
188
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000189 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000190 if len(extension) == 0:
191 # no extension.
192 return self._default_mime_type
193
ericroman@google.comc17ca532009-05-07 03:51:05 +0000194 # extension starts with a dot, so we need to remove it
195 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000196
initial.commit94958cf2008-07-26 22:42:52 +0000197 def NoCacheMaxAgeTimeHandler(self):
198 """This request handler yields a page with the title set to the current
199 system time, and no caching requested."""
200
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000201 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000202 return False
203
204 self.send_response(200)
205 self.send_header('Cache-Control', 'max-age=0')
206 self.send_header('Content-type', 'text/html')
207 self.end_headers()
208
maruel@google.come250a9b2009-03-10 17:39:46 +0000209 self.wfile.write('<html><head><title>%s</title></head></html>' %
210 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000211
212 return True
213
214 def NoCacheTimeHandler(self):
215 """This request handler yields a page with the title set to the current
216 system time, and no caching requested."""
217
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000218 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000219 return False
220
221 self.send_response(200)
222 self.send_header('Cache-Control', 'no-cache')
223 self.send_header('Content-type', 'text/html')
224 self.end_headers()
225
maruel@google.come250a9b2009-03-10 17:39:46 +0000226 self.wfile.write('<html><head><title>%s</title></head></html>' %
227 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000228
229 return True
230
231 def CacheTimeHandler(self):
232 """This request handler yields a page with the title set to the current
233 system time, and allows caching for one minute."""
234
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000235 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000236 return False
237
238 self.send_response(200)
239 self.send_header('Cache-Control', 'max-age=60')
240 self.send_header('Content-type', 'text/html')
241 self.end_headers()
242
maruel@google.come250a9b2009-03-10 17:39:46 +0000243 self.wfile.write('<html><head><title>%s</title></head></html>' %
244 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000245
246 return True
247
248 def CacheExpiresHandler(self):
249 """This request handler yields a page with the title set to the current
250 system time, and set the page to expire on 1 Jan 2099."""
251
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000252 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000253 return False
254
255 self.send_response(200)
256 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
257 self.send_header('Content-type', 'text/html')
258 self.end_headers()
259
maruel@google.come250a9b2009-03-10 17:39:46 +0000260 self.wfile.write('<html><head><title>%s</title></head></html>' %
261 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000262
263 return True
264
265 def CacheProxyRevalidateHandler(self):
266 """This request handler yields a page with the title set to the current
267 system time, and allows caching for 60 seconds"""
268
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000269 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000270 return False
271
272 self.send_response(200)
273 self.send_header('Content-type', 'text/html')
274 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
275 self.end_headers()
276
maruel@google.come250a9b2009-03-10 17:39:46 +0000277 self.wfile.write('<html><head><title>%s</title></head></html>' %
278 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000279
280 return True
281
282 def CachePrivateHandler(self):
283 """This request handler yields a page with the title set to the current
284 system time, and allows caching for 5 seconds."""
285
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000286 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000287 return False
288
289 self.send_response(200)
290 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000291 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000292 self.end_headers()
293
maruel@google.come250a9b2009-03-10 17:39:46 +0000294 self.wfile.write('<html><head><title>%s</title></head></html>' %
295 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000296
297 return True
298
299 def CachePublicHandler(self):
300 """This request handler yields a page with the title set to the current
301 system time, and allows caching for 5 seconds."""
302
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000303 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000304 return False
305
306 self.send_response(200)
307 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000308 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000309 self.end_headers()
310
maruel@google.come250a9b2009-03-10 17:39:46 +0000311 self.wfile.write('<html><head><title>%s</title></head></html>' %
312 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000313
314 return True
315
316 def CacheSMaxAgeHandler(self):
317 """This request handler yields a page with the title set to the current
318 system time, and does not allow for caching."""
319
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000320 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000321 return False
322
323 self.send_response(200)
324 self.send_header('Content-type', 'text/html')
325 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
326 self.end_headers()
327
maruel@google.come250a9b2009-03-10 17:39:46 +0000328 self.wfile.write('<html><head><title>%s</title></head></html>' %
329 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000330
331 return True
332
333 def CacheMustRevalidateHandler(self):
334 """This request handler yields a page with the title set to the current
335 system time, and does not allow caching."""
336
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000337 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000338 return False
339
340 self.send_response(200)
341 self.send_header('Content-type', 'text/html')
342 self.send_header('Cache-Control', 'must-revalidate')
343 self.end_headers()
344
maruel@google.come250a9b2009-03-10 17:39:46 +0000345 self.wfile.write('<html><head><title>%s</title></head></html>' %
346 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000347
348 return True
349
350 def CacheMustRevalidateMaxAgeHandler(self):
351 """This request handler yields a page with the title set to the current
352 system time, and does not allow caching event though max-age of 60
353 seconds is specified."""
354
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000355 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000356 return False
357
358 self.send_response(200)
359 self.send_header('Content-type', 'text/html')
360 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
361 self.end_headers()
362
maruel@google.come250a9b2009-03-10 17:39:46 +0000363 self.wfile.write('<html><head><title>%s</title></head></html>' %
364 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000365
366 return True
367
initial.commit94958cf2008-07-26 22:42:52 +0000368 def CacheNoStoreHandler(self):
369 """This request handler yields a page with the title set to the current
370 system time, and does not allow the page to be stored."""
371
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000372 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000373 return False
374
375 self.send_response(200)
376 self.send_header('Content-type', 'text/html')
377 self.send_header('Cache-Control', 'no-store')
378 self.end_headers()
379
maruel@google.come250a9b2009-03-10 17:39:46 +0000380 self.wfile.write('<html><head><title>%s</title></head></html>' %
381 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000382
383 return True
384
385 def CacheNoStoreMaxAgeHandler(self):
386 """This request handler yields a page with the title set to the current
387 system time, and does not allow the page to be stored even though max-age
388 of 60 seconds is specified."""
389
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000390 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000391 return False
392
393 self.send_response(200)
394 self.send_header('Content-type', 'text/html')
395 self.send_header('Cache-Control', 'max-age=60, no-store')
396 self.end_headers()
397
maruel@google.come250a9b2009-03-10 17:39:46 +0000398 self.wfile.write('<html><head><title>%s</title></head></html>' %
399 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000400
401 return True
402
403
404 def CacheNoTransformHandler(self):
405 """This request handler yields a page with the title set to the current
406 system time, and does not allow the content to transformed during
407 user-agent caching"""
408
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000409 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000410 return False
411
412 self.send_response(200)
413 self.send_header('Content-type', 'text/html')
414 self.send_header('Cache-Control', 'no-transform')
415 self.end_headers()
416
maruel@google.come250a9b2009-03-10 17:39:46 +0000417 self.wfile.write('<html><head><title>%s</title></head></html>' %
418 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000419
420 return True
421
422 def EchoHeader(self):
423 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000424 """The only difference between this function and the EchoHeaderOverride"""
425 """function is in the parameter being passed to the helper function"""
426 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000427
ananta@chromium.org219b2062009-10-23 16:09:41 +0000428 def EchoHeaderOverride(self):
429 """This handler echoes back the value of a specific request header."""
430 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
431 """IE to issue HTTP requests using the host network stack."""
432 """The Accept and Charset tests which expect the server to echo back"""
433 """the corresponding headers fail here as IE returns cached responses"""
434 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
435 """treats this request as a new request and does not cache it."""
436 return self.EchoHeaderHelper("/echoheaderoverride")
437
438 def EchoHeaderHelper(self, echo_header):
439 """This function echoes back the value of the request header passed in."""
440 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000441 return False
442
443 query_char = self.path.find('?')
444 if query_char != -1:
445 header_name = self.path[query_char+1:]
446
447 self.send_response(200)
448 self.send_header('Content-type', 'text/plain')
449 self.send_header('Cache-control', 'max-age=60000')
450 # insert a vary header to properly indicate that the cachability of this
451 # request is subject to value of the request header being echoed.
452 if len(header_name) > 0:
453 self.send_header('Vary', header_name)
454 self.end_headers()
455
456 if len(header_name) > 0:
457 self.wfile.write(self.headers.getheader(header_name))
458
459 return True
460
461 def EchoHandler(self):
462 """This handler just echoes back the payload of the request, for testing
463 form submission."""
464
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000465 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000466 return False
467
468 self.send_response(200)
469 self.send_header('Content-type', 'text/html')
470 self.end_headers()
471 length = int(self.headers.getheader('content-length'))
472 request = self.rfile.read(length)
473 self.wfile.write(request)
474 return True
475
476 def EchoTitleHandler(self):
477 """This handler is like Echo, but sets the page title to the request."""
478
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479 if not self._ShouldHandleRequest("/echotitle"):
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('<html><head><title>')
488 self.wfile.write(request)
489 self.wfile.write('</title></head></html>')
490 return True
491
492 def EchoAllHandler(self):
493 """This handler yields a (more) human-readable page listing information
494 about the request header & contents."""
495
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000496 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000497 return False
498
499 self.send_response(200)
500 self.send_header('Content-type', 'text/html')
501 self.end_headers()
502 self.wfile.write('<html><head><style>'
503 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
504 '</style></head><body>'
505 '<div style="float: right">'
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +0000506 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000507 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000508
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000509 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000510 length = int(self.headers.getheader('content-length'))
511 qs = self.rfile.read(length)
512 params = cgi.parse_qs(qs, keep_blank_values=1)
513
514 for param in params:
515 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000516
517 self.wfile.write('</pre>')
518
519 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
520
521 self.wfile.write('</body></html>')
522 return True
523
524 def DownloadHandler(self):
525 """This handler sends a downloadable file with or without reporting
526 the size (6K)."""
527
528 if self.path.startswith("/download-unknown-size"):
529 send_length = False
530 elif self.path.startswith("/download-known-size"):
531 send_length = True
532 else:
533 return False
534
535 #
536 # The test which uses this functionality is attempting to send
537 # small chunks of data to the client. Use a fairly large buffer
538 # so that we'll fill chrome's IO buffer enough to force it to
539 # actually write the data.
540 # See also the comments in the client-side of this test in
541 # download_uitest.cc
542 #
543 size_chunk1 = 35*1024
544 size_chunk2 = 10*1024
545
546 self.send_response(200)
547 self.send_header('Content-type', 'application/octet-stream')
548 self.send_header('Cache-Control', 'max-age=0')
549 if send_length:
550 self.send_header('Content-Length', size_chunk1 + size_chunk2)
551 self.end_headers()
552
553 # First chunk of data:
554 self.wfile.write("*" * size_chunk1)
555 self.wfile.flush()
556
557 # handle requests until one of them clears this flag.
558 self.server.waitForDownload = True
559 while self.server.waitForDownload:
560 self.server.handle_request()
561
562 # Second chunk of data:
563 self.wfile.write("*" * size_chunk2)
564 return True
565
566 def DownloadFinishHandler(self):
567 """This handler just tells the server to finish the current download."""
568
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000569 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000570 return False
571
572 self.server.waitForDownload = False
573 self.send_response(200)
574 self.send_header('Content-type', 'text/html')
575 self.send_header('Cache-Control', 'max-age=0')
576 self.end_headers()
577 return True
578
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000579 def _ReplaceFileData(self, data, query_parameters):
580 """Replaces matching substrings in a file.
581
582 If the 'replace_orig' and 'replace_new' URL query parameters are present,
583 a new string is returned with all occasions of the 'replace_orig' value
584 replaced by the 'replace_new' value.
585
586 If the parameters are not present, |data| is returned.
587 """
588 query_dict = cgi.parse_qs(query_parameters)
589 orig_values = query_dict.get('replace_orig', [])
590 new_values = query_dict.get('replace_new', [])
591 if not orig_values or not new_values:
592 return data
593 orig_value = orig_values[0]
594 new_value = new_values[0]
595 return data.replace(orig_value, new_value)
596
initial.commit94958cf2008-07-26 22:42:52 +0000597 def FileHandler(self):
598 """This handler sends the contents of the requested file. Wow, it's like
599 a real webserver!"""
600
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000601 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000602 if not self.path.startswith(prefix):
603 return False
604
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000605 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000606 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000607 self.rfile.read(int(self.headers.getheader('content-length')))
608
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000609 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
610 sub_path = url_path[len(prefix):]
611 entries = sub_path.split('/')
612 file_path = os.path.join(self.server.data_dir, *entries)
613 if os.path.isdir(file_path):
614 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000615
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000616 if not os.path.isfile(file_path):
617 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000618 self.send_error(404)
619 return True
620
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000621 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000622 data = f.read()
623 f.close()
624
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000625 data = self._ReplaceFileData(data, query)
626
initial.commit94958cf2008-07-26 22:42:52 +0000627 # If file.mock-http-headers exists, it contains the headers we
628 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000629 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000630 if os.path.isfile(headers_path):
631 f = open(headers_path, "r")
632
633 # "HTTP/1.1 200 OK"
634 response = f.readline()
635 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
636 self.send_response(int(status_code))
637
638 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000639 header_values = re.findall('(\S+):\s*(.*)', line)
640 if len(header_values) > 0:
641 # "name: value"
642 name, value = header_values[0]
643 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000644 f.close()
645 else:
646 # Could be more generic once we support mime-type sniffing, but for
647 # now we need to set it explicitly.
648 self.send_response(200)
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000649 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
initial.commit94958cf2008-07-26 22:42:52 +0000650 self.send_header('Content-Length', len(data))
651 self.end_headers()
652
653 self.wfile.write(data)
654
655 return True
656
657 def RealFileWithCommonHeaderHandler(self):
658 """This handler sends the contents of the requested file without the pseudo
659 http head!"""
660
661 prefix='/realfiles/'
662 if not self.path.startswith(prefix):
663 return False
664
665 file = self.path[len(prefix):]
666 path = os.path.join(self.server.data_dir, file)
667
668 try:
669 f = open(path, "rb")
670 data = f.read()
671 f.close()
672
673 # just simply set the MIME as octal stream
674 self.send_response(200)
675 self.send_header('Content-type', 'application/octet-stream')
676 self.end_headers()
677
678 self.wfile.write(data)
679 except:
680 self.send_error(404)
681
682 return True
683
684 def RealBZ2FileWithCommonHeaderHandler(self):
685 """This handler sends the bzip2 contents of the requested file with
686 corresponding Content-Encoding field in http head!"""
687
688 prefix='/realbz2files/'
689 if not self.path.startswith(prefix):
690 return False
691
692 parts = self.path.split('?')
693 file = parts[0][len(prefix):]
694 path = os.path.join(self.server.data_dir, file) + '.bz2'
695
696 if len(parts) > 1:
697 options = parts[1]
698 else:
699 options = ''
700
701 try:
702 self.send_response(200)
703 accept_encoding = self.headers.get("Accept-Encoding")
704 if accept_encoding.find("bzip2") != -1:
705 f = open(path, "rb")
706 data = f.read()
707 f.close()
708 self.send_header('Content-Encoding', 'bzip2')
709 self.send_header('Content-type', 'application/x-bzip2')
710 self.end_headers()
711 if options == 'incremental-header':
712 self.wfile.write(data[:1])
713 self.wfile.flush()
714 time.sleep(1.0)
715 self.wfile.write(data[1:])
716 else:
717 self.wfile.write(data)
718 else:
719 """client do not support bzip2 format, send pseudo content
720 """
721 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
722 self.end_headers()
723 self.wfile.write("you do not support bzip2 encoding")
724 except:
725 self.send_error(404)
726
727 return True
728
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000729 def SetCookieHandler(self):
730 """This handler just sets a cookie, for testing cookie handling."""
731
732 if not self._ShouldHandleRequest("/set-cookie"):
733 return False
734
735 query_char = self.path.find('?')
736 if query_char != -1:
737 cookie_values = self.path[query_char + 1:].split('&')
738 else:
739 cookie_values = ("",)
740 self.send_response(200)
741 self.send_header('Content-type', 'text/html')
742 for cookie_value in cookie_values:
743 self.send_header('Set-Cookie', '%s' % cookie_value)
744 self.end_headers()
745 for cookie_value in cookie_values:
746 self.wfile.write('%s' % cookie_value)
747 return True
748
initial.commit94958cf2008-07-26 22:42:52 +0000749 def AuthBasicHandler(self):
750 """This handler tests 'Basic' authentication. It just sends a page with
751 title 'user/pass' if you succeed."""
752
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000753 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000754 return False
755
756 username = userpass = password = b64str = ""
757
ericroman@google.com239b4d82009-03-27 04:00:22 +0000758 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
759
initial.commit94958cf2008-07-26 22:42:52 +0000760 auth = self.headers.getheader('authorization')
761 try:
762 if not auth:
763 raise Exception('no auth')
764 b64str = re.findall(r'Basic (\S+)', auth)[0]
765 userpass = base64.b64decode(b64str)
766 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
767 if password != 'secret':
768 raise Exception('wrong password')
769 except Exception, e:
770 # Authentication failed.
771 self.send_response(401)
772 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
773 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000774 if set_cookie_if_challenged:
775 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000776 self.end_headers()
777 self.wfile.write('<html><head>')
778 self.wfile.write('<title>Denied: %s</title>' % e)
779 self.wfile.write('</head><body>')
780 self.wfile.write('auth=%s<p>' % auth)
781 self.wfile.write('b64str=%s<p>' % b64str)
782 self.wfile.write('username: %s<p>' % username)
783 self.wfile.write('userpass: %s<p>' % userpass)
784 self.wfile.write('password: %s<p>' % password)
785 self.wfile.write('You sent:<br>%s<p>' % self.headers)
786 self.wfile.write('</body></html>')
787 return True
788
789 # Authentication successful. (Return a cachable response to allow for
790 # testing cached pages that require authentication.)
791 if_none_match = self.headers.getheader('if-none-match')
792 if if_none_match == "abc":
793 self.send_response(304)
794 self.end_headers()
795 else:
796 self.send_response(200)
797 self.send_header('Content-type', 'text/html')
798 self.send_header('Cache-control', 'max-age=60000')
799 self.send_header('Etag', 'abc')
800 self.end_headers()
801 self.wfile.write('<html><head>')
802 self.wfile.write('<title>%s/%s</title>' % (username, password))
803 self.wfile.write('</head><body>')
804 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000805 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000806 self.wfile.write('</body></html>')
807
808 return True
809
tonyg@chromium.org75054202010-03-31 22:06:10 +0000810 def GetNonce(self, force_reset=False):
811 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000812
tonyg@chromium.org75054202010-03-31 22:06:10 +0000813 This is a fake implementation. A real implementation would only use a given
814 nonce a single time (hence the name n-once). However, for the purposes of
815 unittesting, we don't care about the security of the nonce.
816
817 Args:
818 force_reset: Iff set, the nonce will be changed. Useful for testing the
819 "stale" response.
820 """
821 if force_reset or not self.server.nonce_time:
822 self.server.nonce_time = time.time()
823 return _new_md5('privatekey%s%d' %
824 (self.path, self.server.nonce_time)).hexdigest()
825
826 def AuthDigestHandler(self):
827 """This handler tests 'Digest' authentication.
828
829 It just sends a page with title 'user/pass' if you succeed.
830
831 A stale response is sent iff "stale" is present in the request path.
832 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000833 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000834 return False
835
tonyg@chromium.org75054202010-03-31 22:06:10 +0000836 stale = 'stale' in self.path
837 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000838 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000839 password = 'secret'
840 realm = 'testrealm'
841
842 auth = self.headers.getheader('authorization')
843 pairs = {}
844 try:
845 if not auth:
846 raise Exception('no auth')
847 if not auth.startswith('Digest'):
848 raise Exception('not digest')
849 # Pull out all the name="value" pairs as a dictionary.
850 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
851
852 # Make sure it's all valid.
853 if pairs['nonce'] != nonce:
854 raise Exception('wrong nonce')
855 if pairs['opaque'] != opaque:
856 raise Exception('wrong opaque')
857
858 # Check the 'response' value and make sure it matches our magic hash.
859 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000860 hash_a1 = _new_md5(
861 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000862 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000863 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000864 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000865 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
866 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000867 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000868
869 if pairs['response'] != response:
870 raise Exception('wrong password')
871 except Exception, e:
872 # Authentication failed.
873 self.send_response(401)
874 hdr = ('Digest '
875 'realm="%s", '
876 'domain="/", '
877 'qop="auth", '
878 'algorithm=MD5, '
879 'nonce="%s", '
880 'opaque="%s"') % (realm, nonce, opaque)
881 if stale:
882 hdr += ', stale="TRUE"'
883 self.send_header('WWW-Authenticate', hdr)
884 self.send_header('Content-type', 'text/html')
885 self.end_headers()
886 self.wfile.write('<html><head>')
887 self.wfile.write('<title>Denied: %s</title>' % e)
888 self.wfile.write('</head><body>')
889 self.wfile.write('auth=%s<p>' % auth)
890 self.wfile.write('pairs=%s<p>' % pairs)
891 self.wfile.write('You sent:<br>%s<p>' % self.headers)
892 self.wfile.write('We are replying:<br>%s<p>' % hdr)
893 self.wfile.write('</body></html>')
894 return True
895
896 # Authentication successful.
897 self.send_response(200)
898 self.send_header('Content-type', 'text/html')
899 self.end_headers()
900 self.wfile.write('<html><head>')
901 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
902 self.wfile.write('</head><body>')
903 self.wfile.write('auth=%s<p>' % auth)
904 self.wfile.write('pairs=%s<p>' % pairs)
905 self.wfile.write('</body></html>')
906
907 return True
908
909 def SlowServerHandler(self):
910 """Wait for the user suggested time before responding. The syntax is
911 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000912 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000913 return False
914 query_char = self.path.find('?')
915 wait_sec = 1.0
916 if query_char >= 0:
917 try:
918 wait_sec = int(self.path[query_char + 1:])
919 except ValueError:
920 pass
921 time.sleep(wait_sec)
922 self.send_response(200)
923 self.send_header('Content-type', 'text/plain')
924 self.end_headers()
925 self.wfile.write("waited %d seconds" % wait_sec)
926 return True
927
928 def ContentTypeHandler(self):
929 """Returns a string of html with the given content type. E.g.,
930 /contenttype?text/css returns an html file with the Content-Type
931 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000932 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000933 return False
934 query_char = self.path.find('?')
935 content_type = self.path[query_char + 1:].strip()
936 if not content_type:
937 content_type = 'text/html'
938 self.send_response(200)
939 self.send_header('Content-Type', content_type)
940 self.end_headers()
941 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
942 return True
943
944 def ServerRedirectHandler(self):
945 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000946 '/server-redirect?http://foo.bar/asdf' to redirect to
947 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000948
949 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000950 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000951 return False
952
953 query_char = self.path.find('?')
954 if query_char < 0 or len(self.path) <= query_char + 1:
955 self.sendRedirectHelp(test_name)
956 return True
957 dest = self.path[query_char + 1:]
958
959 self.send_response(301) # moved permanently
960 self.send_header('Location', dest)
961 self.send_header('Content-type', 'text/html')
962 self.end_headers()
963 self.wfile.write('<html><head>')
964 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
965
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000966 return True
initial.commit94958cf2008-07-26 22:42:52 +0000967
968 def ClientRedirectHandler(self):
969 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000970 '/client-redirect?http://foo.bar/asdf' to redirect to
971 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000972
973 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000974 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000975 return False
976
977 query_char = self.path.find('?');
978 if query_char < 0 or len(self.path) <= query_char + 1:
979 self.sendRedirectHelp(test_name)
980 return True
981 dest = self.path[query_char + 1:]
982
983 self.send_response(200)
984 self.send_header('Content-type', 'text/html')
985 self.end_headers()
986 self.wfile.write('<html><head>')
987 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
988 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
989
990 return True
991
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000992 def ChromiumSyncTimeHandler(self):
993 """Handle Chromium sync .../time requests.
994
995 The syncer sometimes checks server reachability by examining /time.
996 """
997 test_name = "/chromiumsync/time"
998 if not self._ShouldHandleRequest(test_name):
999 return False
1000
1001 self.send_response(200)
1002 self.send_header('Content-type', 'text/html')
1003 self.end_headers()
1004 return True
1005
1006 def ChromiumSyncCommandHandler(self):
1007 """Handle a chromiumsync command arriving via http.
1008
1009 This covers all sync protocol commands: authentication, getupdates, and
1010 commit.
1011 """
1012 test_name = "/chromiumsync/command"
1013 if not self._ShouldHandleRequest(test_name):
1014 return False
1015
1016 length = int(self.headers.getheader('content-length'))
1017 raw_request = self.rfile.read(length)
1018
pathorn@chromium.org44920122010-07-27 18:25:35 +00001019 if not self.server._sync_handler:
1020 import chromiumsync
1021 self.server._sync_handler = chromiumsync.TestServer()
1022 http_response, raw_reply = self.server._sync_handler.HandleCommand(
nick@chromium.org06b8a662010-09-22 22:50:18 +00001023 self.path, raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001024 self.send_response(http_response)
1025 self.end_headers()
1026 self.wfile.write(raw_reply)
1027 return True
1028
tony@chromium.org03266982010-03-05 03:18:42 +00001029 def MultipartHandler(self):
1030 """Send a multipart response (10 text/html pages)."""
1031 test_name = "/multipart"
1032 if not self._ShouldHandleRequest(test_name):
1033 return False
1034
1035 num_frames = 10
1036 bound = '12345'
1037 self.send_response(200)
1038 self.send_header('Content-type',
1039 'multipart/x-mixed-replace;boundary=' + bound)
1040 self.end_headers()
1041
1042 for i in xrange(num_frames):
1043 self.wfile.write('--' + bound + '\r\n')
1044 self.wfile.write('Content-type: text/html\r\n\r\n')
1045 self.wfile.write('<title>page ' + str(i) + '</title>')
1046 self.wfile.write('page ' + str(i))
1047
1048 self.wfile.write('--' + bound + '--')
1049 return True
1050
initial.commit94958cf2008-07-26 22:42:52 +00001051 def DefaultResponseHandler(self):
1052 """This is the catch-all response handler for requests that aren't handled
1053 by one of the special handlers above.
1054 Note that we specify the content-length as without it the https connection
1055 is not closed properly (and the browser keeps expecting data)."""
1056
1057 contents = "Default response given for path: " + self.path
1058 self.send_response(200)
1059 self.send_header('Content-type', 'text/html')
1060 self.send_header("Content-Length", len(contents))
1061 self.end_headers()
1062 self.wfile.write(contents)
1063 return True
1064
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001065 def RedirectConnectHandler(self):
1066 """Sends a redirect to the CONNECT request for www.redirect.com. This
1067 response is not specified by the RFC, so the browser should not follow
1068 the redirect."""
1069
1070 if (self.path.find("www.redirect.com") < 0):
1071 return False
1072
1073 dest = "http://www.destination.com/foo.js"
1074
1075 self.send_response(302) # moved temporarily
1076 self.send_header('Location', dest)
1077 self.send_header('Connection', 'close')
1078 self.end_headers()
1079 return True
1080
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001081 def ServerAuthConnectHandler(self):
1082 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1083 response doesn't make sense because the proxy server cannot request
1084 server authentication."""
1085
1086 if (self.path.find("www.server-auth.com") < 0):
1087 return False
1088
1089 challenge = 'Basic realm="WallyWorld"'
1090
1091 self.send_response(401) # unauthorized
1092 self.send_header('WWW-Authenticate', challenge)
1093 self.send_header('Connection', 'close')
1094 self.end_headers()
1095 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001096
1097 def DefaultConnectResponseHandler(self):
1098 """This is the catch-all response handler for CONNECT requests that aren't
1099 handled by one of the special handlers above. Real Web servers respond
1100 with 400 to CONNECT requests."""
1101
1102 contents = "Your client has issued a malformed or illegal request."
1103 self.send_response(400) # bad request
1104 self.send_header('Content-type', 'text/html')
1105 self.send_header("Content-Length", len(contents))
1106 self.end_headers()
1107 self.wfile.write(contents)
1108 return True
1109
1110 def do_CONNECT(self):
1111 for handler in self._connect_handlers:
1112 if handler():
1113 return
1114
initial.commit94958cf2008-07-26 22:42:52 +00001115 def do_GET(self):
1116 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001117 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001118 return
1119
1120 def do_POST(self):
1121 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001122 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001123 return
1124
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001125 def do_PUT(self):
1126 for handler in self._put_handlers:
1127 if handler():
1128 return
1129
initial.commit94958cf2008-07-26 22:42:52 +00001130 # called by the redirect handling function when there is no parameter
1131 def sendRedirectHelp(self, redirect_name):
1132 self.send_response(200)
1133 self.send_header('Content-type', 'text/html')
1134 self.end_headers()
1135 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1136 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1137 self.wfile.write('</body></html>')
1138
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001139def MakeDataDir():
1140 if options.data_dir:
1141 if not os.path.isdir(options.data_dir):
1142 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1143 return None
1144 my_data_dir = options.data_dir
1145 else:
1146 # Create the default path to our data dir, relative to the exe dir.
1147 my_data_dir = os.path.dirname(sys.argv[0])
1148 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001149 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001150
1151 #TODO(ibrar): Must use Find* funtion defined in google\tools
1152 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1153
1154 return my_data_dir
1155
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001156class FileMultiplexer:
1157 def __init__(self, fd1, fd2) :
1158 self.__fd1 = fd1
1159 self.__fd2 = fd2
1160
1161 def __del__(self) :
1162 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1163 self.__fd1.close()
1164 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1165 self.__fd2.close()
1166
1167 def write(self, text) :
1168 self.__fd1.write(text)
1169 self.__fd2.write(text)
1170
1171 def flush(self) :
1172 self.__fd1.flush()
1173 self.__fd2.flush()
1174
initial.commit94958cf2008-07-26 22:42:52 +00001175def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001176 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001177 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1178 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001179
1180 port = options.port
1181
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001182 if options.server_type == SERVER_HTTP:
1183 if options.cert:
1184 # let's make sure the cert file exists.
1185 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001186 print 'specified server cert file not found: ' + options.cert + \
1187 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001188 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001189 for ca_cert in options.ssl_client_ca:
1190 if not os.path.isfile(ca_cert):
1191 print 'specified trusted client CA file not found: ' + ca_cert + \
1192 ' exiting...'
1193 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001194 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001195 options.ssl_client_auth, options.ssl_client_ca,
1196 options.ssl_bulk_cipher)
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +00001197 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001198 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001199 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +00001200 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001201
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001202 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001203 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001204 server._sync_handler = None
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +00001205 listen_port = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001206 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001207 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001208 my_data_dir = MakeDataDir()
1209
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001210 # 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
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001222
1223 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001224 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1225 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001226
1227 # Instantiate FTP server class and listen to 127.0.0.1:port
1228 address = ('127.0.0.1', port)
1229 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +00001230 listen_port = server.socket.getsockname()[1]
1231 print 'FTP server started on port %d...' % listen_port
initial.commit94958cf2008-07-26 22:42:52 +00001232
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001233 # Notify the parent that we've started. (BaseServer subclasses
1234 # bind their sockets on construction.)
1235 if options.startup_pipe is not None:
1236 if sys.platform == 'win32':
1237 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1238 else:
1239 fd = options.startup_pipe
1240 startup_pipe = os.fdopen(fd, "w")
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +00001241 # Write the listening port as a 2 byte value. This is _not_ using
1242 # network byte ordering since the other end of the pipe is on the same
1243 # machine.
1244 startup_pipe.write(struct.pack('@H', listen_port))
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001245 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.')
cbentzel@chromium.orgc86b7af2010-11-09 01:57:24 +00001259 option_parser.add_option('', '--port', default='0', type='int',
1260 help='Port used by the server. If unspecified, the '
1261 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001262 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001263 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001264 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001265 help='Specify that https should be used, specify '
1266 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001267 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001268 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1269 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001270 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1271 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001272 'should include the CA named in the subject of '
1273 'the DER-encoded certificate contained in the '
1274 'specified file. This option may appear multiple '
1275 'times, indicating multiple CA names should be '
1276 'sent in the request.')
1277 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1278 help='Specify the bulk encryption algorithm(s)'
1279 'that will be accepted by the SSL server. Valid '
1280 'values are "aes256", "aes128", "3des", "rc4". If '
1281 'omitted, all algorithms will be used. This '
1282 'option may appear multiple times, indicating '
1283 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001284 option_parser.add_option('', '--file-root-url', default='/files/',
1285 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001286 option_parser.add_option('', '--startup-pipe', type='int',
1287 dest='startup_pipe',
1288 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001289 options, args = option_parser.parse_args()
1290
1291 sys.exit(main(options, args))