blob: ccb589b9e801ed8cc4768f05c2293b4faf6aed3d [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.org3da3d592010-11-09 03:40:22 +00009It defaults to living on localhost:8888.
initial.commit94958cf2008-07-26 22:42:52 +000010It 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.
initial.commit94958cf2008-07-26 22:42:52 +000012"""
13
14import base64
15import BaseHTTPServer
16import cgi
initial.commit94958cf2008-07-26 22:42:52 +000017import optparse
18import os
19import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000020import shutil
initial.commit94958cf2008-07-26 22:42:52 +000021import SocketServer
22import sys
23import time
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000024import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000025import warnings
26
27# Ignore deprecation warnings, they make our output more cluttered.
28warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000029
30import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000031import tlslite
32import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000033
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000034try:
35 import hashlib
36 _new_md5 = hashlib.md5
37except ImportError:
38 import md5
39 _new_md5 = md5.new
40
davidben@chromium.org06fcf202010-09-22 18:15:23 +000041if sys.platform == 'win32':
42 import msvcrt
43
maruel@chromium.org756cf982009-03-05 12:46:38 +000044SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000045SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000046
47debug_output = sys.stderr
48def debug(str):
49 debug_output.write(str + "\n")
50 debug_output.flush()
51
52class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
53 """This is a specialization of of BaseHTTPServer to allow it
54 to be exited cleanly (by setting its "stop" member to True)."""
55
56 def serve_forever(self):
57 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000058 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000059 while not self.stop:
60 self.handle_request()
61 self.socket.close()
62
63class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
64 """This is a specialization of StoppableHTTPerver that add https support."""
65
davidben@chromium.org31282a12010-08-07 01:10:02 +000066 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000067 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000068 s = open(cert_path).read()
69 x509 = tlslite.api.X509()
70 x509.parse(s)
71 self.cert_chain = tlslite.api.X509CertChain([x509])
72 s = open(cert_path).read()
73 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000074 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000075 self.ssl_client_cas = []
76 for ca_file in ssl_client_cas:
77 s = open(ca_file).read()
78 x509 = tlslite.api.X509()
79 x509.parse(s)
80 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000081 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
82 if ssl_bulk_ciphers is not None:
83 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000084
85 self.session_cache = tlslite.api.SessionCache()
86 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
87
88 def handshake(self, tlsConnection):
89 """Creates the SSL connection."""
90 try:
91 tlsConnection.handshakeServer(certChain=self.cert_chain,
92 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000093 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000094 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000095 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000096 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +000097 tlsConnection.ignoreAbruptClose = True
98 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000099 except tlslite.api.TLSAbruptCloseError:
100 # Ignore abrupt close.
101 return True
initial.commit94958cf2008-07-26 22:42:52 +0000102 except tlslite.api.TLSError, error:
103 print "Handshake failure:", str(error)
104 return False
105
106class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
107
108 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000109 self._connect_handlers = [
110 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000111 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000112 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000113 self._get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000114 self.NoCacheMaxAgeTimeHandler,
115 self.NoCacheTimeHandler,
116 self.CacheTimeHandler,
117 self.CacheExpiresHandler,
118 self.CacheProxyRevalidateHandler,
119 self.CachePrivateHandler,
120 self.CachePublicHandler,
121 self.CacheSMaxAgeHandler,
122 self.CacheMustRevalidateHandler,
123 self.CacheMustRevalidateMaxAgeHandler,
124 self.CacheNoStoreHandler,
125 self.CacheNoStoreMaxAgeHandler,
126 self.CacheNoTransformHandler,
127 self.DownloadHandler,
128 self.DownloadFinishHandler,
129 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000130 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000131 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000132 self.FileHandler,
133 self.RealFileWithCommonHeaderHandler,
134 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000135 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000136 self.AuthBasicHandler,
137 self.AuthDigestHandler,
138 self.SlowServerHandler,
139 self.ContentTypeHandler,
140 self.ServerRedirectHandler,
141 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000142 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000143 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000144 self.DefaultResponseHandler]
145 self._post_handlers = [
146 self.EchoTitleHandler,
147 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000148 self.ChromiumSyncCommandHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000149 self.EchoHandler,
150 self.DeviceManagementHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000151 self._put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000152 self.EchoTitleHandler,
153 self.EchoAllHandler,
154 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000155
maruel@google.come250a9b2009-03-10 17:39:46 +0000156 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000157 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000158 'gif': 'image/gif',
159 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000160 'jpg' : 'image/jpeg',
161 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000162 }
initial.commit94958cf2008-07-26 22:42:52 +0000163 self._default_mime_type = 'text/html'
164
maruel@google.come250a9b2009-03-10 17:39:46 +0000165 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
166 client_address,
167 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000168
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000169 def log_request(self, *args, **kwargs):
170 # Disable request logging to declutter test log output.
171 pass
172
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000173 def _ShouldHandleRequest(self, handler_name):
174 """Determines if the path can be handled by the handler.
175
176 We consider a handler valid if the path begins with the
177 handler name. It can optionally be followed by "?*", "/*".
178 """
179
180 pattern = re.compile('%s($|\?|/).*' % handler_name)
181 return pattern.match(self.path)
182
initial.commit94958cf2008-07-26 22:42:52 +0000183 def GetMIMETypeFromName(self, file_name):
184 """Returns the mime type for the specified file_name. So far it only looks
185 at the file extension."""
186
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000187 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000188 if len(extension) == 0:
189 # no extension.
190 return self._default_mime_type
191
ericroman@google.comc17ca532009-05-07 03:51:05 +0000192 # extension starts with a dot, so we need to remove it
193 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000194
initial.commit94958cf2008-07-26 22:42:52 +0000195 def NoCacheMaxAgeTimeHandler(self):
196 """This request handler yields a page with the title set to the current
197 system time, and no caching requested."""
198
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000199 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000200 return False
201
202 self.send_response(200)
203 self.send_header('Cache-Control', 'max-age=0')
204 self.send_header('Content-type', 'text/html')
205 self.end_headers()
206
maruel@google.come250a9b2009-03-10 17:39:46 +0000207 self.wfile.write('<html><head><title>%s</title></head></html>' %
208 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000209
210 return True
211
212 def NoCacheTimeHandler(self):
213 """This request handler yields a page with the title set to the current
214 system time, and no caching requested."""
215
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000216 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000217 return False
218
219 self.send_response(200)
220 self.send_header('Cache-Control', 'no-cache')
221 self.send_header('Content-type', 'text/html')
222 self.end_headers()
223
maruel@google.come250a9b2009-03-10 17:39:46 +0000224 self.wfile.write('<html><head><title>%s</title></head></html>' %
225 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000226
227 return True
228
229 def CacheTimeHandler(self):
230 """This request handler yields a page with the title set to the current
231 system time, and allows caching for one minute."""
232
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000233 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000234 return False
235
236 self.send_response(200)
237 self.send_header('Cache-Control', 'max-age=60')
238 self.send_header('Content-type', 'text/html')
239 self.end_headers()
240
maruel@google.come250a9b2009-03-10 17:39:46 +0000241 self.wfile.write('<html><head><title>%s</title></head></html>' %
242 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000243
244 return True
245
246 def CacheExpiresHandler(self):
247 """This request handler yields a page with the title set to the current
248 system time, and set the page to expire on 1 Jan 2099."""
249
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000250 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000251 return False
252
253 self.send_response(200)
254 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
255 self.send_header('Content-type', 'text/html')
256 self.end_headers()
257
maruel@google.come250a9b2009-03-10 17:39:46 +0000258 self.wfile.write('<html><head><title>%s</title></head></html>' %
259 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000260
261 return True
262
263 def CacheProxyRevalidateHandler(self):
264 """This request handler yields a page with the title set to the current
265 system time, and allows caching for 60 seconds"""
266
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000267 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000268 return False
269
270 self.send_response(200)
271 self.send_header('Content-type', 'text/html')
272 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
273 self.end_headers()
274
maruel@google.come250a9b2009-03-10 17:39:46 +0000275 self.wfile.write('<html><head><title>%s</title></head></html>' %
276 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000277
278 return True
279
280 def CachePrivateHandler(self):
281 """This request handler yields a page with the title set to the current
282 system time, and allows caching for 5 seconds."""
283
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000284 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000285 return False
286
287 self.send_response(200)
288 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000289 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000290 self.end_headers()
291
maruel@google.come250a9b2009-03-10 17:39:46 +0000292 self.wfile.write('<html><head><title>%s</title></head></html>' %
293 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000294
295 return True
296
297 def CachePublicHandler(self):
298 """This request handler yields a page with the title set to the current
299 system time, and allows caching for 5 seconds."""
300
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000301 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000302 return False
303
304 self.send_response(200)
305 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000306 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000307 self.end_headers()
308
maruel@google.come250a9b2009-03-10 17:39:46 +0000309 self.wfile.write('<html><head><title>%s</title></head></html>' %
310 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000311
312 return True
313
314 def CacheSMaxAgeHandler(self):
315 """This request handler yields a page with the title set to the current
316 system time, and does not allow for caching."""
317
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000318 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000319 return False
320
321 self.send_response(200)
322 self.send_header('Content-type', 'text/html')
323 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
324 self.end_headers()
325
maruel@google.come250a9b2009-03-10 17:39:46 +0000326 self.wfile.write('<html><head><title>%s</title></head></html>' %
327 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000328
329 return True
330
331 def CacheMustRevalidateHandler(self):
332 """This request handler yields a page with the title set to the current
333 system time, and does not allow caching."""
334
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000335 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000336 return False
337
338 self.send_response(200)
339 self.send_header('Content-type', 'text/html')
340 self.send_header('Cache-Control', 'must-revalidate')
341 self.end_headers()
342
maruel@google.come250a9b2009-03-10 17:39:46 +0000343 self.wfile.write('<html><head><title>%s</title></head></html>' %
344 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000345
346 return True
347
348 def CacheMustRevalidateMaxAgeHandler(self):
349 """This request handler yields a page with the title set to the current
350 system time, and does not allow caching event though max-age of 60
351 seconds is specified."""
352
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000353 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000354 return False
355
356 self.send_response(200)
357 self.send_header('Content-type', 'text/html')
358 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
359 self.end_headers()
360
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 self.wfile.write('<html><head><title>%s</title></head></html>' %
362 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000363
364 return True
365
initial.commit94958cf2008-07-26 22:42:52 +0000366 def CacheNoStoreHandler(self):
367 """This request handler yields a page with the title set to the current
368 system time, and does not allow the page to be stored."""
369
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000370 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000371 return False
372
373 self.send_response(200)
374 self.send_header('Content-type', 'text/html')
375 self.send_header('Cache-Control', 'no-store')
376 self.end_headers()
377
maruel@google.come250a9b2009-03-10 17:39:46 +0000378 self.wfile.write('<html><head><title>%s</title></head></html>' %
379 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000380
381 return True
382
383 def CacheNoStoreMaxAgeHandler(self):
384 """This request handler yields a page with the title set to the current
385 system time, and does not allow the page to be stored even though max-age
386 of 60 seconds is specified."""
387
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000388 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000389 return False
390
391 self.send_response(200)
392 self.send_header('Content-type', 'text/html')
393 self.send_header('Cache-Control', 'max-age=60, no-store')
394 self.end_headers()
395
maruel@google.come250a9b2009-03-10 17:39:46 +0000396 self.wfile.write('<html><head><title>%s</title></head></html>' %
397 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000398
399 return True
400
401
402 def CacheNoTransformHandler(self):
403 """This request handler yields a page with the title set to the current
404 system time, and does not allow the content to transformed during
405 user-agent caching"""
406
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000407 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000408 return False
409
410 self.send_response(200)
411 self.send_header('Content-type', 'text/html')
412 self.send_header('Cache-Control', 'no-transform')
413 self.end_headers()
414
maruel@google.come250a9b2009-03-10 17:39:46 +0000415 self.wfile.write('<html><head><title>%s</title></head></html>' %
416 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000417
418 return True
419
420 def EchoHeader(self):
421 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000422 """The only difference between this function and the EchoHeaderOverride"""
423 """function is in the parameter being passed to the helper function"""
424 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000425
ananta@chromium.org219b2062009-10-23 16:09:41 +0000426 def EchoHeaderOverride(self):
427 """This handler echoes back the value of a specific request header."""
428 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
429 """IE to issue HTTP requests using the host network stack."""
430 """The Accept and Charset tests which expect the server to echo back"""
431 """the corresponding headers fail here as IE returns cached responses"""
432 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
433 """treats this request as a new request and does not cache it."""
434 return self.EchoHeaderHelper("/echoheaderoverride")
435
436 def EchoHeaderHelper(self, echo_header):
437 """This function echoes back the value of the request header passed in."""
438 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000439 return False
440
441 query_char = self.path.find('?')
442 if query_char != -1:
443 header_name = self.path[query_char+1:]
444
445 self.send_response(200)
446 self.send_header('Content-type', 'text/plain')
447 self.send_header('Cache-control', 'max-age=60000')
448 # insert a vary header to properly indicate that the cachability of this
449 # request is subject to value of the request header being echoed.
450 if len(header_name) > 0:
451 self.send_header('Vary', header_name)
452 self.end_headers()
453
454 if len(header_name) > 0:
455 self.wfile.write(self.headers.getheader(header_name))
456
457 return True
458
459 def EchoHandler(self):
460 """This handler just echoes back the payload of the request, for testing
461 form submission."""
462
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000463 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000464 return False
465
466 self.send_response(200)
467 self.send_header('Content-type', 'text/html')
468 self.end_headers()
469 length = int(self.headers.getheader('content-length'))
470 request = self.rfile.read(length)
471 self.wfile.write(request)
472 return True
473
474 def EchoTitleHandler(self):
475 """This handler is like Echo, but sets the page title to the request."""
476
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000477 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000478 return False
479
480 self.send_response(200)
481 self.send_header('Content-type', 'text/html')
482 self.end_headers()
483 length = int(self.headers.getheader('content-length'))
484 request = self.rfile.read(length)
485 self.wfile.write('<html><head><title>')
486 self.wfile.write(request)
487 self.wfile.write('</title></head></html>')
488 return True
489
490 def EchoAllHandler(self):
491 """This handler yields a (more) human-readable page listing information
492 about the request header & contents."""
493
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000494 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000495 return False
496
497 self.send_response(200)
498 self.send_header('Content-type', 'text/html')
499 self.end_headers()
500 self.wfile.write('<html><head><style>'
501 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
502 '</style></head><body>'
503 '<div style="float: right">'
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +0000504 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000505 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000506
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000507 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000508 length = int(self.headers.getheader('content-length'))
509 qs = self.rfile.read(length)
510 params = cgi.parse_qs(qs, keep_blank_values=1)
511
512 for param in params:
513 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000514
515 self.wfile.write('</pre>')
516
517 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
518
519 self.wfile.write('</body></html>')
520 return True
521
522 def DownloadHandler(self):
523 """This handler sends a downloadable file with or without reporting
524 the size (6K)."""
525
526 if self.path.startswith("/download-unknown-size"):
527 send_length = False
528 elif self.path.startswith("/download-known-size"):
529 send_length = True
530 else:
531 return False
532
533 #
534 # The test which uses this functionality is attempting to send
535 # small chunks of data to the client. Use a fairly large buffer
536 # so that we'll fill chrome's IO buffer enough to force it to
537 # actually write the data.
538 # See also the comments in the client-side of this test in
539 # download_uitest.cc
540 #
541 size_chunk1 = 35*1024
542 size_chunk2 = 10*1024
543
544 self.send_response(200)
545 self.send_header('Content-type', 'application/octet-stream')
546 self.send_header('Cache-Control', 'max-age=0')
547 if send_length:
548 self.send_header('Content-Length', size_chunk1 + size_chunk2)
549 self.end_headers()
550
551 # First chunk of data:
552 self.wfile.write("*" * size_chunk1)
553 self.wfile.flush()
554
555 # handle requests until one of them clears this flag.
556 self.server.waitForDownload = True
557 while self.server.waitForDownload:
558 self.server.handle_request()
559
560 # Second chunk of data:
561 self.wfile.write("*" * size_chunk2)
562 return True
563
564 def DownloadFinishHandler(self):
565 """This handler just tells the server to finish the current download."""
566
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000567 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000568 return False
569
570 self.server.waitForDownload = False
571 self.send_response(200)
572 self.send_header('Content-type', 'text/html')
573 self.send_header('Cache-Control', 'max-age=0')
574 self.end_headers()
575 return True
576
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000577 def _ReplaceFileData(self, data, query_parameters):
578 """Replaces matching substrings in a file.
579
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000580 If the 'replace_text' URL query parameter is present, it is expected to be
581 of the form old_text:new_text, which indicates that any old_text strings in
582 the file are replaced with new_text. Multiple 'replace_text' parameters may
583 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000584
585 If the parameters are not present, |data| is returned.
586 """
587 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000588 replace_text_values = query_dict.get('replace_text', [])
589 for replace_text_value in replace_text_values:
590 replace_text_args = replace_text_value.split(':')
591 if len(replace_text_args) != 2:
592 raise ValueError(
593 'replace_text must be of form old_text:new_text. Actual value: %s' %
594 replace_text_value)
595 old_text_b64, new_text_b64 = replace_text_args
596 old_text = base64.urlsafe_b64decode(old_text_b64)
597 new_text = base64.urlsafe_b64decode(new_text_b64)
598 data = data.replace(old_text, new_text)
599 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000600
initial.commit94958cf2008-07-26 22:42:52 +0000601 def FileHandler(self):
602 """This handler sends the contents of the requested file. Wow, it's like
603 a real webserver!"""
604
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000605 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000606 if not self.path.startswith(prefix):
607 return False
608
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000609 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000610 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000611 self.rfile.read(int(self.headers.getheader('content-length')))
612
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000613 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
614 sub_path = url_path[len(prefix):]
615 entries = sub_path.split('/')
616 file_path = os.path.join(self.server.data_dir, *entries)
617 if os.path.isdir(file_path):
618 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000619
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000620 if not os.path.isfile(file_path):
621 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000622 self.send_error(404)
623 return True
624
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000625 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000626 data = f.read()
627 f.close()
628
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000629 data = self._ReplaceFileData(data, query)
630
initial.commit94958cf2008-07-26 22:42:52 +0000631 # If file.mock-http-headers exists, it contains the headers we
632 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000633 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000634 if os.path.isfile(headers_path):
635 f = open(headers_path, "r")
636
637 # "HTTP/1.1 200 OK"
638 response = f.readline()
639 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
640 self.send_response(int(status_code))
641
642 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000643 header_values = re.findall('(\S+):\s*(.*)', line)
644 if len(header_values) > 0:
645 # "name: value"
646 name, value = header_values[0]
647 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000648 f.close()
649 else:
650 # Could be more generic once we support mime-type sniffing, but for
651 # now we need to set it explicitly.
652 self.send_response(200)
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000653 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
initial.commit94958cf2008-07-26 22:42:52 +0000654 self.send_header('Content-Length', len(data))
655 self.end_headers()
656
657 self.wfile.write(data)
658
659 return True
660
661 def RealFileWithCommonHeaderHandler(self):
662 """This handler sends the contents of the requested file without the pseudo
663 http head!"""
664
665 prefix='/realfiles/'
666 if not self.path.startswith(prefix):
667 return False
668
669 file = self.path[len(prefix):]
670 path = os.path.join(self.server.data_dir, file)
671
672 try:
673 f = open(path, "rb")
674 data = f.read()
675 f.close()
676
677 # just simply set the MIME as octal stream
678 self.send_response(200)
679 self.send_header('Content-type', 'application/octet-stream')
680 self.end_headers()
681
682 self.wfile.write(data)
683 except:
684 self.send_error(404)
685
686 return True
687
688 def RealBZ2FileWithCommonHeaderHandler(self):
689 """This handler sends the bzip2 contents of the requested file with
690 corresponding Content-Encoding field in http head!"""
691
692 prefix='/realbz2files/'
693 if not self.path.startswith(prefix):
694 return False
695
696 parts = self.path.split('?')
697 file = parts[0][len(prefix):]
698 path = os.path.join(self.server.data_dir, file) + '.bz2'
699
700 if len(parts) > 1:
701 options = parts[1]
702 else:
703 options = ''
704
705 try:
706 self.send_response(200)
707 accept_encoding = self.headers.get("Accept-Encoding")
708 if accept_encoding.find("bzip2") != -1:
709 f = open(path, "rb")
710 data = f.read()
711 f.close()
712 self.send_header('Content-Encoding', 'bzip2')
713 self.send_header('Content-type', 'application/x-bzip2')
714 self.end_headers()
715 if options == 'incremental-header':
716 self.wfile.write(data[:1])
717 self.wfile.flush()
718 time.sleep(1.0)
719 self.wfile.write(data[1:])
720 else:
721 self.wfile.write(data)
722 else:
723 """client do not support bzip2 format, send pseudo content
724 """
725 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
726 self.end_headers()
727 self.wfile.write("you do not support bzip2 encoding")
728 except:
729 self.send_error(404)
730
731 return True
732
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000733 def SetCookieHandler(self):
734 """This handler just sets a cookie, for testing cookie handling."""
735
736 if not self._ShouldHandleRequest("/set-cookie"):
737 return False
738
739 query_char = self.path.find('?')
740 if query_char != -1:
741 cookie_values = self.path[query_char + 1:].split('&')
742 else:
743 cookie_values = ("",)
744 self.send_response(200)
745 self.send_header('Content-type', 'text/html')
746 for cookie_value in cookie_values:
747 self.send_header('Set-Cookie', '%s' % cookie_value)
748 self.end_headers()
749 for cookie_value in cookie_values:
750 self.wfile.write('%s' % cookie_value)
751 return True
752
initial.commit94958cf2008-07-26 22:42:52 +0000753 def AuthBasicHandler(self):
754 """This handler tests 'Basic' authentication. It just sends a page with
755 title 'user/pass' if you succeed."""
756
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000757 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000758 return False
759
760 username = userpass = password = b64str = ""
761
ericroman@google.com239b4d82009-03-27 04:00:22 +0000762 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
763
initial.commit94958cf2008-07-26 22:42:52 +0000764 auth = self.headers.getheader('authorization')
765 try:
766 if not auth:
767 raise Exception('no auth')
768 b64str = re.findall(r'Basic (\S+)', auth)[0]
769 userpass = base64.b64decode(b64str)
770 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
771 if password != 'secret':
772 raise Exception('wrong password')
773 except Exception, e:
774 # Authentication failed.
775 self.send_response(401)
776 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
777 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000778 if set_cookie_if_challenged:
779 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000780 self.end_headers()
781 self.wfile.write('<html><head>')
782 self.wfile.write('<title>Denied: %s</title>' % e)
783 self.wfile.write('</head><body>')
784 self.wfile.write('auth=%s<p>' % auth)
785 self.wfile.write('b64str=%s<p>' % b64str)
786 self.wfile.write('username: %s<p>' % username)
787 self.wfile.write('userpass: %s<p>' % userpass)
788 self.wfile.write('password: %s<p>' % password)
789 self.wfile.write('You sent:<br>%s<p>' % self.headers)
790 self.wfile.write('</body></html>')
791 return True
792
793 # Authentication successful. (Return a cachable response to allow for
794 # testing cached pages that require authentication.)
795 if_none_match = self.headers.getheader('if-none-match')
796 if if_none_match == "abc":
797 self.send_response(304)
798 self.end_headers()
799 else:
800 self.send_response(200)
801 self.send_header('Content-type', 'text/html')
802 self.send_header('Cache-control', 'max-age=60000')
803 self.send_header('Etag', 'abc')
804 self.end_headers()
805 self.wfile.write('<html><head>')
806 self.wfile.write('<title>%s/%s</title>' % (username, password))
807 self.wfile.write('</head><body>')
808 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000809 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.wfile.write('</body></html>')
811
812 return True
813
tonyg@chromium.org75054202010-03-31 22:06:10 +0000814 def GetNonce(self, force_reset=False):
815 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000816
tonyg@chromium.org75054202010-03-31 22:06:10 +0000817 This is a fake implementation. A real implementation would only use a given
818 nonce a single time (hence the name n-once). However, for the purposes of
819 unittesting, we don't care about the security of the nonce.
820
821 Args:
822 force_reset: Iff set, the nonce will be changed. Useful for testing the
823 "stale" response.
824 """
825 if force_reset or not self.server.nonce_time:
826 self.server.nonce_time = time.time()
827 return _new_md5('privatekey%s%d' %
828 (self.path, self.server.nonce_time)).hexdigest()
829
830 def AuthDigestHandler(self):
831 """This handler tests 'Digest' authentication.
832
833 It just sends a page with title 'user/pass' if you succeed.
834
835 A stale response is sent iff "stale" is present in the request path.
836 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000837 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000838 return False
839
tonyg@chromium.org75054202010-03-31 22:06:10 +0000840 stale = 'stale' in self.path
841 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000842 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000843 password = 'secret'
844 realm = 'testrealm'
845
846 auth = self.headers.getheader('authorization')
847 pairs = {}
848 try:
849 if not auth:
850 raise Exception('no auth')
851 if not auth.startswith('Digest'):
852 raise Exception('not digest')
853 # Pull out all the name="value" pairs as a dictionary.
854 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
855
856 # Make sure it's all valid.
857 if pairs['nonce'] != nonce:
858 raise Exception('wrong nonce')
859 if pairs['opaque'] != opaque:
860 raise Exception('wrong opaque')
861
862 # Check the 'response' value and make sure it matches our magic hash.
863 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000864 hash_a1 = _new_md5(
865 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000866 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000867 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000868 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000869 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
870 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000871 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000872
873 if pairs['response'] != response:
874 raise Exception('wrong password')
875 except Exception, e:
876 # Authentication failed.
877 self.send_response(401)
878 hdr = ('Digest '
879 'realm="%s", '
880 'domain="/", '
881 'qop="auth", '
882 'algorithm=MD5, '
883 'nonce="%s", '
884 'opaque="%s"') % (realm, nonce, opaque)
885 if stale:
886 hdr += ', stale="TRUE"'
887 self.send_header('WWW-Authenticate', hdr)
888 self.send_header('Content-type', 'text/html')
889 self.end_headers()
890 self.wfile.write('<html><head>')
891 self.wfile.write('<title>Denied: %s</title>' % e)
892 self.wfile.write('</head><body>')
893 self.wfile.write('auth=%s<p>' % auth)
894 self.wfile.write('pairs=%s<p>' % pairs)
895 self.wfile.write('You sent:<br>%s<p>' % self.headers)
896 self.wfile.write('We are replying:<br>%s<p>' % hdr)
897 self.wfile.write('</body></html>')
898 return True
899
900 # Authentication successful.
901 self.send_response(200)
902 self.send_header('Content-type', 'text/html')
903 self.end_headers()
904 self.wfile.write('<html><head>')
905 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
906 self.wfile.write('</head><body>')
907 self.wfile.write('auth=%s<p>' % auth)
908 self.wfile.write('pairs=%s<p>' % pairs)
909 self.wfile.write('</body></html>')
910
911 return True
912
913 def SlowServerHandler(self):
914 """Wait for the user suggested time before responding. The syntax is
915 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000916 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000917 return False
918 query_char = self.path.find('?')
919 wait_sec = 1.0
920 if query_char >= 0:
921 try:
922 wait_sec = int(self.path[query_char + 1:])
923 except ValueError:
924 pass
925 time.sleep(wait_sec)
926 self.send_response(200)
927 self.send_header('Content-type', 'text/plain')
928 self.end_headers()
929 self.wfile.write("waited %d seconds" % wait_sec)
930 return True
931
932 def ContentTypeHandler(self):
933 """Returns a string of html with the given content type. E.g.,
934 /contenttype?text/css returns an html file with the Content-Type
935 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000936 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000937 return False
938 query_char = self.path.find('?')
939 content_type = self.path[query_char + 1:].strip()
940 if not content_type:
941 content_type = 'text/html'
942 self.send_response(200)
943 self.send_header('Content-Type', content_type)
944 self.end_headers()
945 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
946 return True
947
948 def ServerRedirectHandler(self):
949 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000950 '/server-redirect?http://foo.bar/asdf' to redirect to
951 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000952
953 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000954 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000955 return False
956
957 query_char = self.path.find('?')
958 if query_char < 0 or len(self.path) <= query_char + 1:
959 self.sendRedirectHelp(test_name)
960 return True
961 dest = self.path[query_char + 1:]
962
963 self.send_response(301) # moved permanently
964 self.send_header('Location', dest)
965 self.send_header('Content-type', 'text/html')
966 self.end_headers()
967 self.wfile.write('<html><head>')
968 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
969
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000970 return True
initial.commit94958cf2008-07-26 22:42:52 +0000971
972 def ClientRedirectHandler(self):
973 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000974 '/client-redirect?http://foo.bar/asdf' to redirect to
975 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000976
977 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000978 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000979 return False
980
981 query_char = self.path.find('?');
982 if query_char < 0 or len(self.path) <= query_char + 1:
983 self.sendRedirectHelp(test_name)
984 return True
985 dest = self.path[query_char + 1:]
986
987 self.send_response(200)
988 self.send_header('Content-type', 'text/html')
989 self.end_headers()
990 self.wfile.write('<html><head>')
991 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
992 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
993
994 return True
995
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000996 def ChromiumSyncTimeHandler(self):
997 """Handle Chromium sync .../time requests.
998
999 The syncer sometimes checks server reachability by examining /time.
1000 """
1001 test_name = "/chromiumsync/time"
1002 if not self._ShouldHandleRequest(test_name):
1003 return False
1004
1005 self.send_response(200)
1006 self.send_header('Content-type', 'text/html')
1007 self.end_headers()
1008 return True
1009
1010 def ChromiumSyncCommandHandler(self):
1011 """Handle a chromiumsync command arriving via http.
1012
1013 This covers all sync protocol commands: authentication, getupdates, and
1014 commit.
1015 """
1016 test_name = "/chromiumsync/command"
1017 if not self._ShouldHandleRequest(test_name):
1018 return False
1019
1020 length = int(self.headers.getheader('content-length'))
1021 raw_request = self.rfile.read(length)
1022
pathorn@chromium.org44920122010-07-27 18:25:35 +00001023 if not self.server._sync_handler:
1024 import chromiumsync
1025 self.server._sync_handler = chromiumsync.TestServer()
1026 http_response, raw_reply = self.server._sync_handler.HandleCommand(
nick@chromium.org06b8a662010-09-22 22:50:18 +00001027 self.path, raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001028 self.send_response(http_response)
1029 self.end_headers()
1030 self.wfile.write(raw_reply)
1031 return True
1032
tony@chromium.org03266982010-03-05 03:18:42 +00001033 def MultipartHandler(self):
1034 """Send a multipart response (10 text/html pages)."""
1035 test_name = "/multipart"
1036 if not self._ShouldHandleRequest(test_name):
1037 return False
1038
1039 num_frames = 10
1040 bound = '12345'
1041 self.send_response(200)
1042 self.send_header('Content-type',
1043 'multipart/x-mixed-replace;boundary=' + bound)
1044 self.end_headers()
1045
1046 for i in xrange(num_frames):
1047 self.wfile.write('--' + bound + '\r\n')
1048 self.wfile.write('Content-type: text/html\r\n\r\n')
1049 self.wfile.write('<title>page ' + str(i) + '</title>')
1050 self.wfile.write('page ' + str(i))
1051
1052 self.wfile.write('--' + bound + '--')
1053 return True
1054
initial.commit94958cf2008-07-26 22:42:52 +00001055 def DefaultResponseHandler(self):
1056 """This is the catch-all response handler for requests that aren't handled
1057 by one of the special handlers above.
1058 Note that we specify the content-length as without it the https connection
1059 is not closed properly (and the browser keeps expecting data)."""
1060
1061 contents = "Default response given for path: " + self.path
1062 self.send_response(200)
1063 self.send_header('Content-type', 'text/html')
1064 self.send_header("Content-Length", len(contents))
1065 self.end_headers()
1066 self.wfile.write(contents)
1067 return True
1068
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001069 def RedirectConnectHandler(self):
1070 """Sends a redirect to the CONNECT request for www.redirect.com. This
1071 response is not specified by the RFC, so the browser should not follow
1072 the redirect."""
1073
1074 if (self.path.find("www.redirect.com") < 0):
1075 return False
1076
1077 dest = "http://www.destination.com/foo.js"
1078
1079 self.send_response(302) # moved temporarily
1080 self.send_header('Location', dest)
1081 self.send_header('Connection', 'close')
1082 self.end_headers()
1083 return True
1084
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001085 def ServerAuthConnectHandler(self):
1086 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1087 response doesn't make sense because the proxy server cannot request
1088 server authentication."""
1089
1090 if (self.path.find("www.server-auth.com") < 0):
1091 return False
1092
1093 challenge = 'Basic realm="WallyWorld"'
1094
1095 self.send_response(401) # unauthorized
1096 self.send_header('WWW-Authenticate', challenge)
1097 self.send_header('Connection', 'close')
1098 self.end_headers()
1099 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001100
1101 def DefaultConnectResponseHandler(self):
1102 """This is the catch-all response handler for CONNECT requests that aren't
1103 handled by one of the special handlers above. Real Web servers respond
1104 with 400 to CONNECT requests."""
1105
1106 contents = "Your client has issued a malformed or illegal request."
1107 self.send_response(400) # bad request
1108 self.send_header('Content-type', 'text/html')
1109 self.send_header("Content-Length", len(contents))
1110 self.end_headers()
1111 self.wfile.write(contents)
1112 return True
1113
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001114 def DeviceManagementHandler(self):
1115 """Delegates to the device management service used for cloud policy."""
1116 if not self._ShouldHandleRequest("/device_management"):
1117 return False
1118
1119 length = int(self.headers.getheader('content-length'))
1120 raw_request = self.rfile.read(length)
1121
1122 if not self.server._device_management_handler:
1123 import device_management
1124 policy_path = os.path.join(self.server.data_dir, 'device_management')
1125 self.server._device_management_handler = (
1126 device_management.TestServer(policy_path))
1127
1128 http_response, raw_reply = (
1129 self.server._device_management_handler.HandleRequest(self.path,
1130 self.headers,
1131 raw_request))
1132 self.send_response(http_response)
1133 self.end_headers()
1134 self.wfile.write(raw_reply)
1135 return True
1136
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001137 def do_CONNECT(self):
1138 for handler in self._connect_handlers:
1139 if handler():
1140 return
1141
initial.commit94958cf2008-07-26 22:42:52 +00001142 def do_GET(self):
1143 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001144 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001145 return
1146
1147 def do_POST(self):
1148 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001149 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001150 return
1151
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001152 def do_PUT(self):
1153 for handler in self._put_handlers:
1154 if handler():
1155 return
1156
initial.commit94958cf2008-07-26 22:42:52 +00001157 # called by the redirect handling function when there is no parameter
1158 def sendRedirectHelp(self, redirect_name):
1159 self.send_response(200)
1160 self.send_header('Content-type', 'text/html')
1161 self.end_headers()
1162 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1163 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1164 self.wfile.write('</body></html>')
1165
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001166def MakeDataDir():
1167 if options.data_dir:
1168 if not os.path.isdir(options.data_dir):
1169 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1170 return None
1171 my_data_dir = options.data_dir
1172 else:
1173 # Create the default path to our data dir, relative to the exe dir.
1174 my_data_dir = os.path.dirname(sys.argv[0])
1175 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001176 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001177
1178 #TODO(ibrar): Must use Find* funtion defined in google\tools
1179 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1180
1181 return my_data_dir
1182
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001183class FileMultiplexer:
1184 def __init__(self, fd1, fd2) :
1185 self.__fd1 = fd1
1186 self.__fd2 = fd2
1187
1188 def __del__(self) :
1189 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1190 self.__fd1.close()
1191 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1192 self.__fd2.close()
1193
1194 def write(self, text) :
1195 self.__fd1.write(text)
1196 self.__fd2.write(text)
1197
1198 def flush(self) :
1199 self.__fd1.flush()
1200 self.__fd2.flush()
1201
initial.commit94958cf2008-07-26 22:42:52 +00001202def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001203 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001204 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1205 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001206
1207 port = options.port
1208
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001209 if options.server_type == SERVER_HTTP:
1210 if options.cert:
1211 # let's make sure the cert file exists.
1212 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001213 print 'specified server cert file not found: ' + options.cert + \
1214 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001215 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001216 for ca_cert in options.ssl_client_ca:
1217 if not os.path.isfile(ca_cert):
1218 print 'specified trusted client CA file not found: ' + ca_cert + \
1219 ' exiting...'
1220 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001221 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001222 options.ssl_client_auth, options.ssl_client_ca,
1223 options.ssl_bulk_cipher)
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001224 print 'HTTPS server started on port %d...' % port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001225 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001226 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001227 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001228
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001229 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001230 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001231 server._sync_handler = None
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001232 server._device_management_handler = None
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001233
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001234 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001235 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001236 my_data_dir = MakeDataDir()
1237
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001238 # Instantiate a dummy authorizer for managing 'virtual' users
1239 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1240
1241 # Define a new user having full r/w permissions and a read-only
1242 # anonymous user
1243 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1244
1245 authorizer.add_anonymous(my_data_dir)
1246
1247 # Instantiate FTP handler class
1248 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1249 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001250
1251 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001252 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1253 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001254
1255 # Instantiate FTP server class and listen to 127.0.0.1:port
1256 address = ('127.0.0.1', port)
1257 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001258 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001259
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001260 # Notify the parent that we've started. (BaseServer subclasses
1261 # bind their sockets on construction.)
1262 if options.startup_pipe is not None:
1263 if sys.platform == 'win32':
1264 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1265 else:
1266 fd = options.startup_pipe
1267 startup_pipe = os.fdopen(fd, "w")
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001268 startup_pipe.write("READY")
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001269 startup_pipe.close()
1270
initial.commit94958cf2008-07-26 22:42:52 +00001271 try:
1272 server.serve_forever()
1273 except KeyboardInterrupt:
1274 print 'shutting down server'
1275 server.stop = True
1276
1277if __name__ == '__main__':
1278 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001279 option_parser.add_option("-f", '--ftp', action='store_const',
1280 const=SERVER_FTP, default=SERVER_HTTP,
1281 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001282 help='FTP or HTTP server: default is HTTP.')
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001283 option_parser.add_option('', '--port', default='8888', type='int',
1284 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001285 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001286 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001287 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001288 help='Specify that https should be used, specify '
1289 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001290 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001291 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1292 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001293 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1294 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001295 'should include the CA named in the subject of '
1296 'the DER-encoded certificate contained in the '
1297 'specified file. This option may appear multiple '
1298 'times, indicating multiple CA names should be '
1299 'sent in the request.')
1300 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1301 help='Specify the bulk encryption algorithm(s)'
1302 'that will be accepted by the SSL server. Valid '
1303 'values are "aes256", "aes128", "3des", "rc4". If '
1304 'omitted, all algorithms will be used. This '
1305 'option may appear multiple times, indicating '
1306 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001307 option_parser.add_option('', '--file-root-url', default='/files/',
1308 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001309 option_parser.add_option('', '--startup-pipe', type='int',
1310 dest='startup_pipe',
1311 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001312 options, args = option_parser.parse_args()
1313
1314 sys.exit(main(options, args))