blob: 53444e3a6dd8104de1ae141919c6d84f04ced804 [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
580 If the 'replace_orig' and 'replace_new' URL query parameters are present,
581 a new string is returned with all occasions of the 'replace_orig' value
582 replaced by the 'replace_new' value.
583
584 If the parameters are not present, |data| is returned.
585 """
586 query_dict = cgi.parse_qs(query_parameters)
587 orig_values = query_dict.get('replace_orig', [])
588 new_values = query_dict.get('replace_new', [])
589 if not orig_values or not new_values:
590 return data
591 orig_value = orig_values[0]
592 new_value = new_values[0]
593 return data.replace(orig_value, new_value)
594
initial.commit94958cf2008-07-26 22:42:52 +0000595 def FileHandler(self):
596 """This handler sends the contents of the requested file. Wow, it's like
597 a real webserver!"""
598
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000599 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000600 if not self.path.startswith(prefix):
601 return False
602
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000603 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000604 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000605 self.rfile.read(int(self.headers.getheader('content-length')))
606
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000607 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
608 sub_path = url_path[len(prefix):]
609 entries = sub_path.split('/')
610 file_path = os.path.join(self.server.data_dir, *entries)
611 if os.path.isdir(file_path):
612 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000613
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000614 if not os.path.isfile(file_path):
615 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000616 self.send_error(404)
617 return True
618
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000619 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000620 data = f.read()
621 f.close()
622
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000623 data = self._ReplaceFileData(data, query)
624
initial.commit94958cf2008-07-26 22:42:52 +0000625 # If file.mock-http-headers exists, it contains the headers we
626 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000627 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000628 if os.path.isfile(headers_path):
629 f = open(headers_path, "r")
630
631 # "HTTP/1.1 200 OK"
632 response = f.readline()
633 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
634 self.send_response(int(status_code))
635
636 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000637 header_values = re.findall('(\S+):\s*(.*)', line)
638 if len(header_values) > 0:
639 # "name: value"
640 name, value = header_values[0]
641 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000642 f.close()
643 else:
644 # Could be more generic once we support mime-type sniffing, but for
645 # now we need to set it explicitly.
646 self.send_response(200)
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000647 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
initial.commit94958cf2008-07-26 22:42:52 +0000648 self.send_header('Content-Length', len(data))
649 self.end_headers()
650
651 self.wfile.write(data)
652
653 return True
654
655 def RealFileWithCommonHeaderHandler(self):
656 """This handler sends the contents of the requested file without the pseudo
657 http head!"""
658
659 prefix='/realfiles/'
660 if not self.path.startswith(prefix):
661 return False
662
663 file = self.path[len(prefix):]
664 path = os.path.join(self.server.data_dir, file)
665
666 try:
667 f = open(path, "rb")
668 data = f.read()
669 f.close()
670
671 # just simply set the MIME as octal stream
672 self.send_response(200)
673 self.send_header('Content-type', 'application/octet-stream')
674 self.end_headers()
675
676 self.wfile.write(data)
677 except:
678 self.send_error(404)
679
680 return True
681
682 def RealBZ2FileWithCommonHeaderHandler(self):
683 """This handler sends the bzip2 contents of the requested file with
684 corresponding Content-Encoding field in http head!"""
685
686 prefix='/realbz2files/'
687 if not self.path.startswith(prefix):
688 return False
689
690 parts = self.path.split('?')
691 file = parts[0][len(prefix):]
692 path = os.path.join(self.server.data_dir, file) + '.bz2'
693
694 if len(parts) > 1:
695 options = parts[1]
696 else:
697 options = ''
698
699 try:
700 self.send_response(200)
701 accept_encoding = self.headers.get("Accept-Encoding")
702 if accept_encoding.find("bzip2") != -1:
703 f = open(path, "rb")
704 data = f.read()
705 f.close()
706 self.send_header('Content-Encoding', 'bzip2')
707 self.send_header('Content-type', 'application/x-bzip2')
708 self.end_headers()
709 if options == 'incremental-header':
710 self.wfile.write(data[:1])
711 self.wfile.flush()
712 time.sleep(1.0)
713 self.wfile.write(data[1:])
714 else:
715 self.wfile.write(data)
716 else:
717 """client do not support bzip2 format, send pseudo content
718 """
719 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
720 self.end_headers()
721 self.wfile.write("you do not support bzip2 encoding")
722 except:
723 self.send_error(404)
724
725 return True
726
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000727 def SetCookieHandler(self):
728 """This handler just sets a cookie, for testing cookie handling."""
729
730 if not self._ShouldHandleRequest("/set-cookie"):
731 return False
732
733 query_char = self.path.find('?')
734 if query_char != -1:
735 cookie_values = self.path[query_char + 1:].split('&')
736 else:
737 cookie_values = ("",)
738 self.send_response(200)
739 self.send_header('Content-type', 'text/html')
740 for cookie_value in cookie_values:
741 self.send_header('Set-Cookie', '%s' % cookie_value)
742 self.end_headers()
743 for cookie_value in cookie_values:
744 self.wfile.write('%s' % cookie_value)
745 return True
746
initial.commit94958cf2008-07-26 22:42:52 +0000747 def AuthBasicHandler(self):
748 """This handler tests 'Basic' authentication. It just sends a page with
749 title 'user/pass' if you succeed."""
750
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000751 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000752 return False
753
754 username = userpass = password = b64str = ""
755
ericroman@google.com239b4d82009-03-27 04:00:22 +0000756 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
757
initial.commit94958cf2008-07-26 22:42:52 +0000758 auth = self.headers.getheader('authorization')
759 try:
760 if not auth:
761 raise Exception('no auth')
762 b64str = re.findall(r'Basic (\S+)', auth)[0]
763 userpass = base64.b64decode(b64str)
764 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
765 if password != 'secret':
766 raise Exception('wrong password')
767 except Exception, e:
768 # Authentication failed.
769 self.send_response(401)
770 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
771 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000772 if set_cookie_if_challenged:
773 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000774 self.end_headers()
775 self.wfile.write('<html><head>')
776 self.wfile.write('<title>Denied: %s</title>' % e)
777 self.wfile.write('</head><body>')
778 self.wfile.write('auth=%s<p>' % auth)
779 self.wfile.write('b64str=%s<p>' % b64str)
780 self.wfile.write('username: %s<p>' % username)
781 self.wfile.write('userpass: %s<p>' % userpass)
782 self.wfile.write('password: %s<p>' % password)
783 self.wfile.write('You sent:<br>%s<p>' % self.headers)
784 self.wfile.write('</body></html>')
785 return True
786
787 # Authentication successful. (Return a cachable response to allow for
788 # testing cached pages that require authentication.)
789 if_none_match = self.headers.getheader('if-none-match')
790 if if_none_match == "abc":
791 self.send_response(304)
792 self.end_headers()
793 else:
794 self.send_response(200)
795 self.send_header('Content-type', 'text/html')
796 self.send_header('Cache-control', 'max-age=60000')
797 self.send_header('Etag', 'abc')
798 self.end_headers()
799 self.wfile.write('<html><head>')
800 self.wfile.write('<title>%s/%s</title>' % (username, password))
801 self.wfile.write('</head><body>')
802 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000803 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000804 self.wfile.write('</body></html>')
805
806 return True
807
tonyg@chromium.org75054202010-03-31 22:06:10 +0000808 def GetNonce(self, force_reset=False):
809 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000810
tonyg@chromium.org75054202010-03-31 22:06:10 +0000811 This is a fake implementation. A real implementation would only use a given
812 nonce a single time (hence the name n-once). However, for the purposes of
813 unittesting, we don't care about the security of the nonce.
814
815 Args:
816 force_reset: Iff set, the nonce will be changed. Useful for testing the
817 "stale" response.
818 """
819 if force_reset or not self.server.nonce_time:
820 self.server.nonce_time = time.time()
821 return _new_md5('privatekey%s%d' %
822 (self.path, self.server.nonce_time)).hexdigest()
823
824 def AuthDigestHandler(self):
825 """This handler tests 'Digest' authentication.
826
827 It just sends a page with title 'user/pass' if you succeed.
828
829 A stale response is sent iff "stale" is present in the request path.
830 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000831 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000832 return False
833
tonyg@chromium.org75054202010-03-31 22:06:10 +0000834 stale = 'stale' in self.path
835 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000836 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000837 password = 'secret'
838 realm = 'testrealm'
839
840 auth = self.headers.getheader('authorization')
841 pairs = {}
842 try:
843 if not auth:
844 raise Exception('no auth')
845 if not auth.startswith('Digest'):
846 raise Exception('not digest')
847 # Pull out all the name="value" pairs as a dictionary.
848 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
849
850 # Make sure it's all valid.
851 if pairs['nonce'] != nonce:
852 raise Exception('wrong nonce')
853 if pairs['opaque'] != opaque:
854 raise Exception('wrong opaque')
855
856 # Check the 'response' value and make sure it matches our magic hash.
857 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000858 hash_a1 = _new_md5(
859 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000860 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000861 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000862 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000863 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
864 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000865 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000866
867 if pairs['response'] != response:
868 raise Exception('wrong password')
869 except Exception, e:
870 # Authentication failed.
871 self.send_response(401)
872 hdr = ('Digest '
873 'realm="%s", '
874 'domain="/", '
875 'qop="auth", '
876 'algorithm=MD5, '
877 'nonce="%s", '
878 'opaque="%s"') % (realm, nonce, opaque)
879 if stale:
880 hdr += ', stale="TRUE"'
881 self.send_header('WWW-Authenticate', hdr)
882 self.send_header('Content-type', 'text/html')
883 self.end_headers()
884 self.wfile.write('<html><head>')
885 self.wfile.write('<title>Denied: %s</title>' % e)
886 self.wfile.write('</head><body>')
887 self.wfile.write('auth=%s<p>' % auth)
888 self.wfile.write('pairs=%s<p>' % pairs)
889 self.wfile.write('You sent:<br>%s<p>' % self.headers)
890 self.wfile.write('We are replying:<br>%s<p>' % hdr)
891 self.wfile.write('</body></html>')
892 return True
893
894 # Authentication successful.
895 self.send_response(200)
896 self.send_header('Content-type', 'text/html')
897 self.end_headers()
898 self.wfile.write('<html><head>')
899 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
900 self.wfile.write('</head><body>')
901 self.wfile.write('auth=%s<p>' % auth)
902 self.wfile.write('pairs=%s<p>' % pairs)
903 self.wfile.write('</body></html>')
904
905 return True
906
907 def SlowServerHandler(self):
908 """Wait for the user suggested time before responding. The syntax is
909 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000910 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000911 return False
912 query_char = self.path.find('?')
913 wait_sec = 1.0
914 if query_char >= 0:
915 try:
916 wait_sec = int(self.path[query_char + 1:])
917 except ValueError:
918 pass
919 time.sleep(wait_sec)
920 self.send_response(200)
921 self.send_header('Content-type', 'text/plain')
922 self.end_headers()
923 self.wfile.write("waited %d seconds" % wait_sec)
924 return True
925
926 def ContentTypeHandler(self):
927 """Returns a string of html with the given content type. E.g.,
928 /contenttype?text/css returns an html file with the Content-Type
929 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000930 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000931 return False
932 query_char = self.path.find('?')
933 content_type = self.path[query_char + 1:].strip()
934 if not content_type:
935 content_type = 'text/html'
936 self.send_response(200)
937 self.send_header('Content-Type', content_type)
938 self.end_headers()
939 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
940 return True
941
942 def ServerRedirectHandler(self):
943 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000944 '/server-redirect?http://foo.bar/asdf' to redirect to
945 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000946
947 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000948 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000949 return False
950
951 query_char = self.path.find('?')
952 if query_char < 0 or len(self.path) <= query_char + 1:
953 self.sendRedirectHelp(test_name)
954 return True
955 dest = self.path[query_char + 1:]
956
957 self.send_response(301) # moved permanently
958 self.send_header('Location', dest)
959 self.send_header('Content-type', 'text/html')
960 self.end_headers()
961 self.wfile.write('<html><head>')
962 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
963
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000964 return True
initial.commit94958cf2008-07-26 22:42:52 +0000965
966 def ClientRedirectHandler(self):
967 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000968 '/client-redirect?http://foo.bar/asdf' to redirect to
969 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000970
971 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000972 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000973 return False
974
975 query_char = self.path.find('?');
976 if query_char < 0 or len(self.path) <= query_char + 1:
977 self.sendRedirectHelp(test_name)
978 return True
979 dest = self.path[query_char + 1:]
980
981 self.send_response(200)
982 self.send_header('Content-type', 'text/html')
983 self.end_headers()
984 self.wfile.write('<html><head>')
985 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
986 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
987
988 return True
989
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000990 def ChromiumSyncTimeHandler(self):
991 """Handle Chromium sync .../time requests.
992
993 The syncer sometimes checks server reachability by examining /time.
994 """
995 test_name = "/chromiumsync/time"
996 if not self._ShouldHandleRequest(test_name):
997 return False
998
999 self.send_response(200)
1000 self.send_header('Content-type', 'text/html')
1001 self.end_headers()
1002 return True
1003
1004 def ChromiumSyncCommandHandler(self):
1005 """Handle a chromiumsync command arriving via http.
1006
1007 This covers all sync protocol commands: authentication, getupdates, and
1008 commit.
1009 """
1010 test_name = "/chromiumsync/command"
1011 if not self._ShouldHandleRequest(test_name):
1012 return False
1013
1014 length = int(self.headers.getheader('content-length'))
1015 raw_request = self.rfile.read(length)
1016
pathorn@chromium.org44920122010-07-27 18:25:35 +00001017 if not self.server._sync_handler:
1018 import chromiumsync
1019 self.server._sync_handler = chromiumsync.TestServer()
1020 http_response, raw_reply = self.server._sync_handler.HandleCommand(
nick@chromium.org06b8a662010-09-22 22:50:18 +00001021 self.path, raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001022 self.send_response(http_response)
1023 self.end_headers()
1024 self.wfile.write(raw_reply)
1025 return True
1026
tony@chromium.org03266982010-03-05 03:18:42 +00001027 def MultipartHandler(self):
1028 """Send a multipart response (10 text/html pages)."""
1029 test_name = "/multipart"
1030 if not self._ShouldHandleRequest(test_name):
1031 return False
1032
1033 num_frames = 10
1034 bound = '12345'
1035 self.send_response(200)
1036 self.send_header('Content-type',
1037 'multipart/x-mixed-replace;boundary=' + bound)
1038 self.end_headers()
1039
1040 for i in xrange(num_frames):
1041 self.wfile.write('--' + bound + '\r\n')
1042 self.wfile.write('Content-type: text/html\r\n\r\n')
1043 self.wfile.write('<title>page ' + str(i) + '</title>')
1044 self.wfile.write('page ' + str(i))
1045
1046 self.wfile.write('--' + bound + '--')
1047 return True
1048
initial.commit94958cf2008-07-26 22:42:52 +00001049 def DefaultResponseHandler(self):
1050 """This is the catch-all response handler for requests that aren't handled
1051 by one of the special handlers above.
1052 Note that we specify the content-length as without it the https connection
1053 is not closed properly (and the browser keeps expecting data)."""
1054
1055 contents = "Default response given for path: " + self.path
1056 self.send_response(200)
1057 self.send_header('Content-type', 'text/html')
1058 self.send_header("Content-Length", len(contents))
1059 self.end_headers()
1060 self.wfile.write(contents)
1061 return True
1062
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001063 def RedirectConnectHandler(self):
1064 """Sends a redirect to the CONNECT request for www.redirect.com. This
1065 response is not specified by the RFC, so the browser should not follow
1066 the redirect."""
1067
1068 if (self.path.find("www.redirect.com") < 0):
1069 return False
1070
1071 dest = "http://www.destination.com/foo.js"
1072
1073 self.send_response(302) # moved temporarily
1074 self.send_header('Location', dest)
1075 self.send_header('Connection', 'close')
1076 self.end_headers()
1077 return True
1078
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001079 def ServerAuthConnectHandler(self):
1080 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1081 response doesn't make sense because the proxy server cannot request
1082 server authentication."""
1083
1084 if (self.path.find("www.server-auth.com") < 0):
1085 return False
1086
1087 challenge = 'Basic realm="WallyWorld"'
1088
1089 self.send_response(401) # unauthorized
1090 self.send_header('WWW-Authenticate', challenge)
1091 self.send_header('Connection', 'close')
1092 self.end_headers()
1093 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001094
1095 def DefaultConnectResponseHandler(self):
1096 """This is the catch-all response handler for CONNECT requests that aren't
1097 handled by one of the special handlers above. Real Web servers respond
1098 with 400 to CONNECT requests."""
1099
1100 contents = "Your client has issued a malformed or illegal request."
1101 self.send_response(400) # bad request
1102 self.send_header('Content-type', 'text/html')
1103 self.send_header("Content-Length", len(contents))
1104 self.end_headers()
1105 self.wfile.write(contents)
1106 return True
1107
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001108 def DeviceManagementHandler(self):
1109 """Delegates to the device management service used for cloud policy."""
1110 if not self._ShouldHandleRequest("/device_management"):
1111 return False
1112
1113 length = int(self.headers.getheader('content-length'))
1114 raw_request = self.rfile.read(length)
1115
1116 if not self.server._device_management_handler:
1117 import device_management
1118 policy_path = os.path.join(self.server.data_dir, 'device_management')
1119 self.server._device_management_handler = (
1120 device_management.TestServer(policy_path))
1121
1122 http_response, raw_reply = (
1123 self.server._device_management_handler.HandleRequest(self.path,
1124 self.headers,
1125 raw_request))
1126 self.send_response(http_response)
1127 self.end_headers()
1128 self.wfile.write(raw_reply)
1129 return True
1130
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001131 def do_CONNECT(self):
1132 for handler in self._connect_handlers:
1133 if handler():
1134 return
1135
initial.commit94958cf2008-07-26 22:42:52 +00001136 def do_GET(self):
1137 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001138 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001139 return
1140
1141 def do_POST(self):
1142 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001143 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001144 return
1145
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001146 def do_PUT(self):
1147 for handler in self._put_handlers:
1148 if handler():
1149 return
1150
initial.commit94958cf2008-07-26 22:42:52 +00001151 # called by the redirect handling function when there is no parameter
1152 def sendRedirectHelp(self, redirect_name):
1153 self.send_response(200)
1154 self.send_header('Content-type', 'text/html')
1155 self.end_headers()
1156 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1157 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1158 self.wfile.write('</body></html>')
1159
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001160def MakeDataDir():
1161 if options.data_dir:
1162 if not os.path.isdir(options.data_dir):
1163 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1164 return None
1165 my_data_dir = options.data_dir
1166 else:
1167 # Create the default path to our data dir, relative to the exe dir.
1168 my_data_dir = os.path.dirname(sys.argv[0])
1169 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001170 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001171
1172 #TODO(ibrar): Must use Find* funtion defined in google\tools
1173 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1174
1175 return my_data_dir
1176
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001177class FileMultiplexer:
1178 def __init__(self, fd1, fd2) :
1179 self.__fd1 = fd1
1180 self.__fd2 = fd2
1181
1182 def __del__(self) :
1183 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1184 self.__fd1.close()
1185 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1186 self.__fd2.close()
1187
1188 def write(self, text) :
1189 self.__fd1.write(text)
1190 self.__fd2.write(text)
1191
1192 def flush(self) :
1193 self.__fd1.flush()
1194 self.__fd2.flush()
1195
initial.commit94958cf2008-07-26 22:42:52 +00001196def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001197 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001198 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1199 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001200
1201 port = options.port
1202
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001203 if options.server_type == SERVER_HTTP:
1204 if options.cert:
1205 # let's make sure the cert file exists.
1206 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001207 print 'specified server cert file not found: ' + options.cert + \
1208 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001209 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001210 for ca_cert in options.ssl_client_ca:
1211 if not os.path.isfile(ca_cert):
1212 print 'specified trusted client CA file not found: ' + ca_cert + \
1213 ' exiting...'
1214 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001215 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001216 options.ssl_client_auth, options.ssl_client_ca,
1217 options.ssl_bulk_cipher)
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001218 print 'HTTPS server started on port %d...' % port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001219 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001220 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001221 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001222
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001223 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001224 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001225 server._sync_handler = None
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001226 server._device_management_handler = None
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001227
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001228 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001229 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001230 my_data_dir = MakeDataDir()
1231
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001232 # Instantiate a dummy authorizer for managing 'virtual' users
1233 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1234
1235 # Define a new user having full r/w permissions and a read-only
1236 # anonymous user
1237 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1238
1239 authorizer.add_anonymous(my_data_dir)
1240
1241 # Instantiate FTP handler class
1242 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1243 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001244
1245 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001246 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1247 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001248
1249 # Instantiate FTP server class and listen to 127.0.0.1:port
1250 address = ('127.0.0.1', port)
1251 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001252 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001253
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001254 # Notify the parent that we've started. (BaseServer subclasses
1255 # bind their sockets on construction.)
1256 if options.startup_pipe is not None:
1257 if sys.platform == 'win32':
1258 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1259 else:
1260 fd = options.startup_pipe
1261 startup_pipe = os.fdopen(fd, "w")
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001262 startup_pipe.write("READY")
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001263 startup_pipe.close()
1264
initial.commit94958cf2008-07-26 22:42:52 +00001265 try:
1266 server.serve_forever()
1267 except KeyboardInterrupt:
1268 print 'shutting down server'
1269 server.stop = True
1270
1271if __name__ == '__main__':
1272 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001273 option_parser.add_option("-f", '--ftp', action='store_const',
1274 const=SERVER_FTP, default=SERVER_HTTP,
1275 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001276 help='FTP or HTTP server: default is HTTP.')
cbentzel@chromium.org3da3d592010-11-09 03:40:22 +00001277 option_parser.add_option('', '--port', default='8888', type='int',
1278 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001279 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001280 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001281 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001282 help='Specify that https should be used, specify '
1283 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001284 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001285 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1286 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001287 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1288 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001289 'should include the CA named in the subject of '
1290 'the DER-encoded certificate contained in the '
1291 'specified file. This option may appear multiple '
1292 'times, indicating multiple CA names should be '
1293 'sent in the request.')
1294 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1295 help='Specify the bulk encryption algorithm(s)'
1296 'that will be accepted by the SSL server. Valid '
1297 'values are "aes256", "aes128", "3des", "rc4". If '
1298 'omitted, all algorithms will be used. This '
1299 'option may appear multiple times, indicating '
1300 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001301 option_parser.add_option('', '--file-root-url', default='/files/',
1302 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001303 option_parser.add_option('', '--startup-pipe', type='int',
1304 dest='startup_pipe',
1305 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001306 options, args = option_parser.parse_args()
1307
1308 sys.exit(main(options, args))