blob: 2a13c2bf6e0cf98c7a3a9bd6b2d3acff90d74449 [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.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
16import base64
17import BaseHTTPServer
18import cgi
initial.commit94958cf2008-07-26 22:42:52 +000019import optparse
20import os
21import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000022import shutil
initial.commit94958cf2008-07-26 22:42:52 +000023import SocketServer
24import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000025import struct
initial.commit94958cf2008-07-26 22:42:52 +000026import time
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000027import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000028import warnings
29
30# Ignore deprecation warnings, they make our output more cluttered.
31warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000032
33import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000034import tlslite
35import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000036
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000037try:
38 import hashlib
39 _new_md5 = hashlib.md5
40except ImportError:
41 import md5
42 _new_md5 = md5.new
43
davidben@chromium.org06fcf202010-09-22 18:15:23 +000044if sys.platform == 'win32':
45 import msvcrt
46
maruel@chromium.org756cf982009-03-05 12:46:38 +000047SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000048SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000049SERVER_SYNC = 2
initial.commit94958cf2008-07-26 22:42:52 +000050
51debug_output = sys.stderr
52def debug(str):
53 debug_output.write(str + "\n")
54 debug_output.flush()
55
56class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
57 """This is a specialization of of BaseHTTPServer to allow it
58 to be exited cleanly (by setting its "stop" member to True)."""
59
60 def serve_forever(self):
61 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000062 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000063 while not self.stop:
64 self.handle_request()
65 self.socket.close()
66
67class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
68 """This is a specialization of StoppableHTTPerver that add https support."""
69
davidben@chromium.org31282a12010-08-07 01:10:02 +000070 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000071 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000072 s = open(cert_path).read()
73 x509 = tlslite.api.X509()
74 x509.parse(s)
75 self.cert_chain = tlslite.api.X509CertChain([x509])
76 s = open(cert_path).read()
77 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000078 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000079 self.ssl_client_cas = []
80 for ca_file in ssl_client_cas:
81 s = open(ca_file).read()
82 x509 = tlslite.api.X509()
83 x509.parse(s)
84 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000085 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
86 if ssl_bulk_ciphers is not None:
87 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000088
89 self.session_cache = tlslite.api.SessionCache()
90 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
91
92 def handshake(self, tlsConnection):
93 """Creates the SSL connection."""
94 try:
95 tlsConnection.handshakeServer(certChain=self.cert_chain,
96 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000097 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000098 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000099 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000100 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000101 tlsConnection.ignoreAbruptClose = True
102 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000103 except tlslite.api.TLSAbruptCloseError:
104 # Ignore abrupt close.
105 return True
initial.commit94958cf2008-07-26 22:42:52 +0000106 except tlslite.api.TLSError, error:
107 print "Handshake failure:", str(error)
108 return False
109
akalin@chromium.org154bb132010-11-12 02:20:27 +0000110
111class SyncHTTPServer(StoppableHTTPServer):
112 """An HTTP server that handles sync commands."""
113
114 def __init__(self, server_address, request_handler_class):
115 # We import here to avoid pulling in chromiumsync's dependencies
116 # unless strictly necessary.
117 import chromiumsync
118 self._sync_handler = chromiumsync.TestServer()
119 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
120
121 def HandleCommand(self, query, raw_request):
122 return self._sync_handler.HandleCommand(query, raw_request)
123
124
125class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
126
127 def __init__(self, request, client_address, socket_server,
128 connect_handlers, get_handlers, post_handlers, put_handlers):
129 self._connect_handlers = connect_handlers
130 self._get_handlers = get_handlers
131 self._post_handlers = post_handlers
132 self._put_handlers = put_handlers
133 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
134 self, request, client_address, socket_server)
135
136 def log_request(self, *args, **kwargs):
137 # Disable request logging to declutter test log output.
138 pass
139
140 def _ShouldHandleRequest(self, handler_name):
141 """Determines if the path can be handled by the handler.
142
143 We consider a handler valid if the path begins with the
144 handler name. It can optionally be followed by "?*", "/*".
145 """
146
147 pattern = re.compile('%s($|\?|/).*' % handler_name)
148 return pattern.match(self.path)
149
150 def do_CONNECT(self):
151 for handler in self._connect_handlers:
152 if handler():
153 return
154
155 def do_GET(self):
156 for handler in self._get_handlers:
157 if handler():
158 return
159
160 def do_POST(self):
161 for handler in self._post_handlers:
162 if handler():
163 return
164
165 def do_PUT(self):
166 for handler in self._put_handlers:
167 if handler():
168 return
169
170
171class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000172
173 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000174 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000175 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000176 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000177 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000178 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000179 self.NoCacheMaxAgeTimeHandler,
180 self.NoCacheTimeHandler,
181 self.CacheTimeHandler,
182 self.CacheExpiresHandler,
183 self.CacheProxyRevalidateHandler,
184 self.CachePrivateHandler,
185 self.CachePublicHandler,
186 self.CacheSMaxAgeHandler,
187 self.CacheMustRevalidateHandler,
188 self.CacheMustRevalidateMaxAgeHandler,
189 self.CacheNoStoreHandler,
190 self.CacheNoStoreMaxAgeHandler,
191 self.CacheNoTransformHandler,
192 self.DownloadHandler,
193 self.DownloadFinishHandler,
194 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000195 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000196 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000197 self.FileHandler,
198 self.RealFileWithCommonHeaderHandler,
199 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000200 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000201 self.AuthBasicHandler,
202 self.AuthDigestHandler,
203 self.SlowServerHandler,
204 self.ContentTypeHandler,
205 self.ServerRedirectHandler,
206 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000207 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000208 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000209 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000210 self.EchoTitleHandler,
211 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000212 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000213 self.DeviceManagementHandler] + get_handlers
214 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000215 self.EchoTitleHandler,
216 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000217 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000218
maruel@google.come250a9b2009-03-10 17:39:46 +0000219 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000220 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000221 'gif': 'image/gif',
222 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000223 'jpg' : 'image/jpeg',
224 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000225 }
initial.commit94958cf2008-07-26 22:42:52 +0000226 self._default_mime_type = 'text/html'
227
akalin@chromium.org154bb132010-11-12 02:20:27 +0000228 BasePageHandler.__init__(self, request, client_address, socket_server,
229 connect_handlers, get_handlers, post_handlers,
230 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000231
initial.commit94958cf2008-07-26 22:42:52 +0000232 def GetMIMETypeFromName(self, file_name):
233 """Returns the mime type for the specified file_name. So far it only looks
234 at the file extension."""
235
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000236 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000237 if len(extension) == 0:
238 # no extension.
239 return self._default_mime_type
240
ericroman@google.comc17ca532009-05-07 03:51:05 +0000241 # extension starts with a dot, so we need to remove it
242 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000243
initial.commit94958cf2008-07-26 22:42:52 +0000244 def NoCacheMaxAgeTimeHandler(self):
245 """This request handler yields a page with the title set to the current
246 system time, and no caching requested."""
247
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000248 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000249 return False
250
251 self.send_response(200)
252 self.send_header('Cache-Control', 'max-age=0')
253 self.send_header('Content-type', 'text/html')
254 self.end_headers()
255
maruel@google.come250a9b2009-03-10 17:39:46 +0000256 self.wfile.write('<html><head><title>%s</title></head></html>' %
257 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000258
259 return True
260
261 def NoCacheTimeHandler(self):
262 """This request handler yields a page with the title set to the current
263 system time, and no caching requested."""
264
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000265 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000266 return False
267
268 self.send_response(200)
269 self.send_header('Cache-Control', 'no-cache')
270 self.send_header('Content-type', 'text/html')
271 self.end_headers()
272
maruel@google.come250a9b2009-03-10 17:39:46 +0000273 self.wfile.write('<html><head><title>%s</title></head></html>' %
274 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000275
276 return True
277
278 def CacheTimeHandler(self):
279 """This request handler yields a page with the title set to the current
280 system time, and allows caching for one minute."""
281
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000282 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000283 return False
284
285 self.send_response(200)
286 self.send_header('Cache-Control', 'max-age=60')
287 self.send_header('Content-type', 'text/html')
288 self.end_headers()
289
maruel@google.come250a9b2009-03-10 17:39:46 +0000290 self.wfile.write('<html><head><title>%s</title></head></html>' %
291 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000292
293 return True
294
295 def CacheExpiresHandler(self):
296 """This request handler yields a page with the title set to the current
297 system time, and set the page to expire on 1 Jan 2099."""
298
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000299 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000300 return False
301
302 self.send_response(200)
303 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
304 self.send_header('Content-type', 'text/html')
305 self.end_headers()
306
maruel@google.come250a9b2009-03-10 17:39:46 +0000307 self.wfile.write('<html><head><title>%s</title></head></html>' %
308 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000309
310 return True
311
312 def CacheProxyRevalidateHandler(self):
313 """This request handler yields a page with the title set to the current
314 system time, and allows caching for 60 seconds"""
315
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000316 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000317 return False
318
319 self.send_response(200)
320 self.send_header('Content-type', 'text/html')
321 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
322 self.end_headers()
323
maruel@google.come250a9b2009-03-10 17:39:46 +0000324 self.wfile.write('<html><head><title>%s</title></head></html>' %
325 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000326
327 return True
328
329 def CachePrivateHandler(self):
330 """This request handler yields a page with the title set to the current
331 system time, and allows caching for 5 seconds."""
332
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000333 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000334 return False
335
336 self.send_response(200)
337 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000338 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000339 self.end_headers()
340
maruel@google.come250a9b2009-03-10 17:39:46 +0000341 self.wfile.write('<html><head><title>%s</title></head></html>' %
342 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000343
344 return True
345
346 def CachePublicHandler(self):
347 """This request handler yields a page with the title set to the current
348 system time, and allows caching for 5 seconds."""
349
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000350 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000351 return False
352
353 self.send_response(200)
354 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000355 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000356 self.end_headers()
357
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 self.wfile.write('<html><head><title>%s</title></head></html>' %
359 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000360
361 return True
362
363 def CacheSMaxAgeHandler(self):
364 """This request handler yields a page with the title set to the current
365 system time, and does not allow for caching."""
366
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000367 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000368 return False
369
370 self.send_response(200)
371 self.send_header('Content-type', 'text/html')
372 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
373 self.end_headers()
374
maruel@google.come250a9b2009-03-10 17:39:46 +0000375 self.wfile.write('<html><head><title>%s</title></head></html>' %
376 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000377
378 return True
379
380 def CacheMustRevalidateHandler(self):
381 """This request handler yields a page with the title set to the current
382 system time, and does not allow caching."""
383
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000385 return False
386
387 self.send_response(200)
388 self.send_header('Content-type', 'text/html')
389 self.send_header('Cache-Control', 'must-revalidate')
390 self.end_headers()
391
maruel@google.come250a9b2009-03-10 17:39:46 +0000392 self.wfile.write('<html><head><title>%s</title></head></html>' %
393 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000394
395 return True
396
397 def CacheMustRevalidateMaxAgeHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and does not allow caching event though max-age of 60
400 seconds is specified."""
401
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000402 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000403 return False
404
405 self.send_response(200)
406 self.send_header('Content-type', 'text/html')
407 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
408 self.end_headers()
409
maruel@google.come250a9b2009-03-10 17:39:46 +0000410 self.wfile.write('<html><head><title>%s</title></head></html>' %
411 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000412
413 return True
414
initial.commit94958cf2008-07-26 22:42:52 +0000415 def CacheNoStoreHandler(self):
416 """This request handler yields a page with the title set to the current
417 system time, and does not allow the page to be stored."""
418
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000419 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000420 return False
421
422 self.send_response(200)
423 self.send_header('Content-type', 'text/html')
424 self.send_header('Cache-Control', 'no-store')
425 self.end_headers()
426
maruel@google.come250a9b2009-03-10 17:39:46 +0000427 self.wfile.write('<html><head><title>%s</title></head></html>' %
428 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000429
430 return True
431
432 def CacheNoStoreMaxAgeHandler(self):
433 """This request handler yields a page with the title set to the current
434 system time, and does not allow the page to be stored even though max-age
435 of 60 seconds is specified."""
436
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000437 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000438 return False
439
440 self.send_response(200)
441 self.send_header('Content-type', 'text/html')
442 self.send_header('Cache-Control', 'max-age=60, no-store')
443 self.end_headers()
444
maruel@google.come250a9b2009-03-10 17:39:46 +0000445 self.wfile.write('<html><head><title>%s</title></head></html>' %
446 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000447
448 return True
449
450
451 def CacheNoTransformHandler(self):
452 """This request handler yields a page with the title set to the current
453 system time, and does not allow the content to transformed during
454 user-agent caching"""
455
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000456 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000457 return False
458
459 self.send_response(200)
460 self.send_header('Content-type', 'text/html')
461 self.send_header('Cache-Control', 'no-transform')
462 self.end_headers()
463
maruel@google.come250a9b2009-03-10 17:39:46 +0000464 self.wfile.write('<html><head><title>%s</title></head></html>' %
465 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000466
467 return True
468
469 def EchoHeader(self):
470 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000471 """The only difference between this function and the EchoHeaderOverride"""
472 """function is in the parameter being passed to the helper function"""
473 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000474
ananta@chromium.org219b2062009-10-23 16:09:41 +0000475 def EchoHeaderOverride(self):
476 """This handler echoes back the value of a specific request header."""
477 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
478 """IE to issue HTTP requests using the host network stack."""
479 """The Accept and Charset tests which expect the server to echo back"""
480 """the corresponding headers fail here as IE returns cached responses"""
481 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
482 """treats this request as a new request and does not cache it."""
483 return self.EchoHeaderHelper("/echoheaderoverride")
484
485 def EchoHeaderHelper(self, echo_header):
486 """This function echoes back the value of the request header passed in."""
487 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000488 return False
489
490 query_char = self.path.find('?')
491 if query_char != -1:
492 header_name = self.path[query_char+1:]
493
494 self.send_response(200)
495 self.send_header('Content-type', 'text/plain')
496 self.send_header('Cache-control', 'max-age=60000')
497 # insert a vary header to properly indicate that the cachability of this
498 # request is subject to value of the request header being echoed.
499 if len(header_name) > 0:
500 self.send_header('Vary', header_name)
501 self.end_headers()
502
503 if len(header_name) > 0:
504 self.wfile.write(self.headers.getheader(header_name))
505
506 return True
507
508 def EchoHandler(self):
509 """This handler just echoes back the payload of the request, for testing
510 form submission."""
511
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000512 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000513 return False
514
515 self.send_response(200)
516 self.send_header('Content-type', 'text/html')
517 self.end_headers()
518 length = int(self.headers.getheader('content-length'))
519 request = self.rfile.read(length)
520 self.wfile.write(request)
521 return True
522
523 def EchoTitleHandler(self):
524 """This handler is like Echo, but sets the page title to the request."""
525
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000526 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000527 return False
528
529 self.send_response(200)
530 self.send_header('Content-type', 'text/html')
531 self.end_headers()
532 length = int(self.headers.getheader('content-length'))
533 request = self.rfile.read(length)
534 self.wfile.write('<html><head><title>')
535 self.wfile.write(request)
536 self.wfile.write('</title></head></html>')
537 return True
538
539 def EchoAllHandler(self):
540 """This handler yields a (more) human-readable page listing information
541 about the request header & contents."""
542
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000543 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000544 return False
545
546 self.send_response(200)
547 self.send_header('Content-type', 'text/html')
548 self.end_headers()
549 self.wfile.write('<html><head><style>'
550 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
551 '</style></head><body>'
552 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000553 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000554 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000555
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000556 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000557 length = int(self.headers.getheader('content-length'))
558 qs = self.rfile.read(length)
559 params = cgi.parse_qs(qs, keep_blank_values=1)
560
561 for param in params:
562 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000563
564 self.wfile.write('</pre>')
565
566 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
567
568 self.wfile.write('</body></html>')
569 return True
570
571 def DownloadHandler(self):
572 """This handler sends a downloadable file with or without reporting
573 the size (6K)."""
574
575 if self.path.startswith("/download-unknown-size"):
576 send_length = False
577 elif self.path.startswith("/download-known-size"):
578 send_length = True
579 else:
580 return False
581
582 #
583 # The test which uses this functionality is attempting to send
584 # small chunks of data to the client. Use a fairly large buffer
585 # so that we'll fill chrome's IO buffer enough to force it to
586 # actually write the data.
587 # See also the comments in the client-side of this test in
588 # download_uitest.cc
589 #
590 size_chunk1 = 35*1024
591 size_chunk2 = 10*1024
592
593 self.send_response(200)
594 self.send_header('Content-type', 'application/octet-stream')
595 self.send_header('Cache-Control', 'max-age=0')
596 if send_length:
597 self.send_header('Content-Length', size_chunk1 + size_chunk2)
598 self.end_headers()
599
600 # First chunk of data:
601 self.wfile.write("*" * size_chunk1)
602 self.wfile.flush()
603
604 # handle requests until one of them clears this flag.
605 self.server.waitForDownload = True
606 while self.server.waitForDownload:
607 self.server.handle_request()
608
609 # Second chunk of data:
610 self.wfile.write("*" * size_chunk2)
611 return True
612
613 def DownloadFinishHandler(self):
614 """This handler just tells the server to finish the current download."""
615
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000616 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000617 return False
618
619 self.server.waitForDownload = False
620 self.send_response(200)
621 self.send_header('Content-type', 'text/html')
622 self.send_header('Cache-Control', 'max-age=0')
623 self.end_headers()
624 return True
625
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000626 def _ReplaceFileData(self, data, query_parameters):
627 """Replaces matching substrings in a file.
628
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000629 If the 'replace_text' URL query parameter is present, it is expected to be
630 of the form old_text:new_text, which indicates that any old_text strings in
631 the file are replaced with new_text. Multiple 'replace_text' parameters may
632 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000633
634 If the parameters are not present, |data| is returned.
635 """
636 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000637 replace_text_values = query_dict.get('replace_text', [])
638 for replace_text_value in replace_text_values:
639 replace_text_args = replace_text_value.split(':')
640 if len(replace_text_args) != 2:
641 raise ValueError(
642 'replace_text must be of form old_text:new_text. Actual value: %s' %
643 replace_text_value)
644 old_text_b64, new_text_b64 = replace_text_args
645 old_text = base64.urlsafe_b64decode(old_text_b64)
646 new_text = base64.urlsafe_b64decode(new_text_b64)
647 data = data.replace(old_text, new_text)
648 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000649
initial.commit94958cf2008-07-26 22:42:52 +0000650 def FileHandler(self):
651 """This handler sends the contents of the requested file. Wow, it's like
652 a real webserver!"""
653
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000654 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000655 if not self.path.startswith(prefix):
656 return False
657
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000658 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000659 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000660 self.rfile.read(int(self.headers.getheader('content-length')))
661
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000662 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
663 sub_path = url_path[len(prefix):]
664 entries = sub_path.split('/')
665 file_path = os.path.join(self.server.data_dir, *entries)
666 if os.path.isdir(file_path):
667 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000668
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000669 if not os.path.isfile(file_path):
670 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000671 self.send_error(404)
672 return True
673
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000674 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000675 data = f.read()
676 f.close()
677
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000678 data = self._ReplaceFileData(data, query)
679
initial.commit94958cf2008-07-26 22:42:52 +0000680 # If file.mock-http-headers exists, it contains the headers we
681 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000682 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000683 if os.path.isfile(headers_path):
684 f = open(headers_path, "r")
685
686 # "HTTP/1.1 200 OK"
687 response = f.readline()
688 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
689 self.send_response(int(status_code))
690
691 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000692 header_values = re.findall('(\S+):\s*(.*)', line)
693 if len(header_values) > 0:
694 # "name: value"
695 name, value = header_values[0]
696 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000697 f.close()
698 else:
699 # Could be more generic once we support mime-type sniffing, but for
700 # now we need to set it explicitly.
701 self.send_response(200)
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000702 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
initial.commit94958cf2008-07-26 22:42:52 +0000703 self.send_header('Content-Length', len(data))
704 self.end_headers()
705
706 self.wfile.write(data)
707
708 return True
709
710 def RealFileWithCommonHeaderHandler(self):
711 """This handler sends the contents of the requested file without the pseudo
712 http head!"""
713
714 prefix='/realfiles/'
715 if not self.path.startswith(prefix):
716 return False
717
718 file = self.path[len(prefix):]
719 path = os.path.join(self.server.data_dir, file)
720
721 try:
722 f = open(path, "rb")
723 data = f.read()
724 f.close()
725
726 # just simply set the MIME as octal stream
727 self.send_response(200)
728 self.send_header('Content-type', 'application/octet-stream')
729 self.end_headers()
730
731 self.wfile.write(data)
732 except:
733 self.send_error(404)
734
735 return True
736
737 def RealBZ2FileWithCommonHeaderHandler(self):
738 """This handler sends the bzip2 contents of the requested file with
739 corresponding Content-Encoding field in http head!"""
740
741 prefix='/realbz2files/'
742 if not self.path.startswith(prefix):
743 return False
744
745 parts = self.path.split('?')
746 file = parts[0][len(prefix):]
747 path = os.path.join(self.server.data_dir, file) + '.bz2'
748
749 if len(parts) > 1:
750 options = parts[1]
751 else:
752 options = ''
753
754 try:
755 self.send_response(200)
756 accept_encoding = self.headers.get("Accept-Encoding")
757 if accept_encoding.find("bzip2") != -1:
758 f = open(path, "rb")
759 data = f.read()
760 f.close()
761 self.send_header('Content-Encoding', 'bzip2')
762 self.send_header('Content-type', 'application/x-bzip2')
763 self.end_headers()
764 if options == 'incremental-header':
765 self.wfile.write(data[:1])
766 self.wfile.flush()
767 time.sleep(1.0)
768 self.wfile.write(data[1:])
769 else:
770 self.wfile.write(data)
771 else:
772 """client do not support bzip2 format, send pseudo content
773 """
774 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
775 self.end_headers()
776 self.wfile.write("you do not support bzip2 encoding")
777 except:
778 self.send_error(404)
779
780 return True
781
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000782 def SetCookieHandler(self):
783 """This handler just sets a cookie, for testing cookie handling."""
784
785 if not self._ShouldHandleRequest("/set-cookie"):
786 return False
787
788 query_char = self.path.find('?')
789 if query_char != -1:
790 cookie_values = self.path[query_char + 1:].split('&')
791 else:
792 cookie_values = ("",)
793 self.send_response(200)
794 self.send_header('Content-type', 'text/html')
795 for cookie_value in cookie_values:
796 self.send_header('Set-Cookie', '%s' % cookie_value)
797 self.end_headers()
798 for cookie_value in cookie_values:
799 self.wfile.write('%s' % cookie_value)
800 return True
801
initial.commit94958cf2008-07-26 22:42:52 +0000802 def AuthBasicHandler(self):
803 """This handler tests 'Basic' authentication. It just sends a page with
804 title 'user/pass' if you succeed."""
805
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000806 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000807 return False
808
809 username = userpass = password = b64str = ""
810
ericroman@google.com239b4d82009-03-27 04:00:22 +0000811 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
812
initial.commit94958cf2008-07-26 22:42:52 +0000813 auth = self.headers.getheader('authorization')
814 try:
815 if not auth:
816 raise Exception('no auth')
817 b64str = re.findall(r'Basic (\S+)', auth)[0]
818 userpass = base64.b64decode(b64str)
819 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
820 if password != 'secret':
821 raise Exception('wrong password')
822 except Exception, e:
823 # Authentication failed.
824 self.send_response(401)
825 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
826 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000827 if set_cookie_if_challenged:
828 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000829 self.end_headers()
830 self.wfile.write('<html><head>')
831 self.wfile.write('<title>Denied: %s</title>' % e)
832 self.wfile.write('</head><body>')
833 self.wfile.write('auth=%s<p>' % auth)
834 self.wfile.write('b64str=%s<p>' % b64str)
835 self.wfile.write('username: %s<p>' % username)
836 self.wfile.write('userpass: %s<p>' % userpass)
837 self.wfile.write('password: %s<p>' % password)
838 self.wfile.write('You sent:<br>%s<p>' % self.headers)
839 self.wfile.write('</body></html>')
840 return True
841
842 # Authentication successful. (Return a cachable response to allow for
843 # testing cached pages that require authentication.)
844 if_none_match = self.headers.getheader('if-none-match')
845 if if_none_match == "abc":
846 self.send_response(304)
847 self.end_headers()
848 else:
849 self.send_response(200)
850 self.send_header('Content-type', 'text/html')
851 self.send_header('Cache-control', 'max-age=60000')
852 self.send_header('Etag', 'abc')
853 self.end_headers()
854 self.wfile.write('<html><head>')
855 self.wfile.write('<title>%s/%s</title>' % (username, password))
856 self.wfile.write('</head><body>')
857 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000858 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000859 self.wfile.write('</body></html>')
860
861 return True
862
tonyg@chromium.org75054202010-03-31 22:06:10 +0000863 def GetNonce(self, force_reset=False):
864 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000865
tonyg@chromium.org75054202010-03-31 22:06:10 +0000866 This is a fake implementation. A real implementation would only use a given
867 nonce a single time (hence the name n-once). However, for the purposes of
868 unittesting, we don't care about the security of the nonce.
869
870 Args:
871 force_reset: Iff set, the nonce will be changed. Useful for testing the
872 "stale" response.
873 """
874 if force_reset or not self.server.nonce_time:
875 self.server.nonce_time = time.time()
876 return _new_md5('privatekey%s%d' %
877 (self.path, self.server.nonce_time)).hexdigest()
878
879 def AuthDigestHandler(self):
880 """This handler tests 'Digest' authentication.
881
882 It just sends a page with title 'user/pass' if you succeed.
883
884 A stale response is sent iff "stale" is present in the request path.
885 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000886 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000887 return False
888
tonyg@chromium.org75054202010-03-31 22:06:10 +0000889 stale = 'stale' in self.path
890 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000891 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000892 password = 'secret'
893 realm = 'testrealm'
894
895 auth = self.headers.getheader('authorization')
896 pairs = {}
897 try:
898 if not auth:
899 raise Exception('no auth')
900 if not auth.startswith('Digest'):
901 raise Exception('not digest')
902 # Pull out all the name="value" pairs as a dictionary.
903 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
904
905 # Make sure it's all valid.
906 if pairs['nonce'] != nonce:
907 raise Exception('wrong nonce')
908 if pairs['opaque'] != opaque:
909 raise Exception('wrong opaque')
910
911 # Check the 'response' value and make sure it matches our magic hash.
912 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000913 hash_a1 = _new_md5(
914 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000915 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000916 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000917 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000918 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
919 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000920 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000921
922 if pairs['response'] != response:
923 raise Exception('wrong password')
924 except Exception, e:
925 # Authentication failed.
926 self.send_response(401)
927 hdr = ('Digest '
928 'realm="%s", '
929 'domain="/", '
930 'qop="auth", '
931 'algorithm=MD5, '
932 'nonce="%s", '
933 'opaque="%s"') % (realm, nonce, opaque)
934 if stale:
935 hdr += ', stale="TRUE"'
936 self.send_header('WWW-Authenticate', hdr)
937 self.send_header('Content-type', 'text/html')
938 self.end_headers()
939 self.wfile.write('<html><head>')
940 self.wfile.write('<title>Denied: %s</title>' % e)
941 self.wfile.write('</head><body>')
942 self.wfile.write('auth=%s<p>' % auth)
943 self.wfile.write('pairs=%s<p>' % pairs)
944 self.wfile.write('You sent:<br>%s<p>' % self.headers)
945 self.wfile.write('We are replying:<br>%s<p>' % hdr)
946 self.wfile.write('</body></html>')
947 return True
948
949 # Authentication successful.
950 self.send_response(200)
951 self.send_header('Content-type', 'text/html')
952 self.end_headers()
953 self.wfile.write('<html><head>')
954 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
955 self.wfile.write('</head><body>')
956 self.wfile.write('auth=%s<p>' % auth)
957 self.wfile.write('pairs=%s<p>' % pairs)
958 self.wfile.write('</body></html>')
959
960 return True
961
962 def SlowServerHandler(self):
963 """Wait for the user suggested time before responding. The syntax is
964 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000965 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000966 return False
967 query_char = self.path.find('?')
968 wait_sec = 1.0
969 if query_char >= 0:
970 try:
971 wait_sec = int(self.path[query_char + 1:])
972 except ValueError:
973 pass
974 time.sleep(wait_sec)
975 self.send_response(200)
976 self.send_header('Content-type', 'text/plain')
977 self.end_headers()
978 self.wfile.write("waited %d seconds" % wait_sec)
979 return True
980
981 def ContentTypeHandler(self):
982 """Returns a string of html with the given content type. E.g.,
983 /contenttype?text/css returns an html file with the Content-Type
984 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000985 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000986 return False
987 query_char = self.path.find('?')
988 content_type = self.path[query_char + 1:].strip()
989 if not content_type:
990 content_type = 'text/html'
991 self.send_response(200)
992 self.send_header('Content-Type', content_type)
993 self.end_headers()
994 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
995 return True
996
997 def ServerRedirectHandler(self):
998 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000999 '/server-redirect?http://foo.bar/asdf' to redirect to
1000 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001001
1002 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001003 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001004 return False
1005
1006 query_char = self.path.find('?')
1007 if query_char < 0 or len(self.path) <= query_char + 1:
1008 self.sendRedirectHelp(test_name)
1009 return True
1010 dest = self.path[query_char + 1:]
1011
1012 self.send_response(301) # moved permanently
1013 self.send_header('Location', dest)
1014 self.send_header('Content-type', 'text/html')
1015 self.end_headers()
1016 self.wfile.write('<html><head>')
1017 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1018
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001019 return True
initial.commit94958cf2008-07-26 22:42:52 +00001020
1021 def ClientRedirectHandler(self):
1022 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001023 '/client-redirect?http://foo.bar/asdf' to redirect to
1024 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001025
1026 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001027 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001028 return False
1029
1030 query_char = self.path.find('?');
1031 if query_char < 0 or len(self.path) <= query_char + 1:
1032 self.sendRedirectHelp(test_name)
1033 return True
1034 dest = self.path[query_char + 1:]
1035
1036 self.send_response(200)
1037 self.send_header('Content-type', 'text/html')
1038 self.end_headers()
1039 self.wfile.write('<html><head>')
1040 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1041 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1042
1043 return True
1044
tony@chromium.org03266982010-03-05 03:18:42 +00001045 def MultipartHandler(self):
1046 """Send a multipart response (10 text/html pages)."""
1047 test_name = "/multipart"
1048 if not self._ShouldHandleRequest(test_name):
1049 return False
1050
1051 num_frames = 10
1052 bound = '12345'
1053 self.send_response(200)
1054 self.send_header('Content-type',
1055 'multipart/x-mixed-replace;boundary=' + bound)
1056 self.end_headers()
1057
1058 for i in xrange(num_frames):
1059 self.wfile.write('--' + bound + '\r\n')
1060 self.wfile.write('Content-type: text/html\r\n\r\n')
1061 self.wfile.write('<title>page ' + str(i) + '</title>')
1062 self.wfile.write('page ' + str(i))
1063
1064 self.wfile.write('--' + bound + '--')
1065 return True
1066
initial.commit94958cf2008-07-26 22:42:52 +00001067 def DefaultResponseHandler(self):
1068 """This is the catch-all response handler for requests that aren't handled
1069 by one of the special handlers above.
1070 Note that we specify the content-length as without it the https connection
1071 is not closed properly (and the browser keeps expecting data)."""
1072
1073 contents = "Default response given for path: " + self.path
1074 self.send_response(200)
1075 self.send_header('Content-type', 'text/html')
1076 self.send_header("Content-Length", len(contents))
1077 self.end_headers()
1078 self.wfile.write(contents)
1079 return True
1080
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001081 def RedirectConnectHandler(self):
1082 """Sends a redirect to the CONNECT request for www.redirect.com. This
1083 response is not specified by the RFC, so the browser should not follow
1084 the redirect."""
1085
1086 if (self.path.find("www.redirect.com") < 0):
1087 return False
1088
1089 dest = "http://www.destination.com/foo.js"
1090
1091 self.send_response(302) # moved temporarily
1092 self.send_header('Location', dest)
1093 self.send_header('Connection', 'close')
1094 self.end_headers()
1095 return True
1096
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001097 def ServerAuthConnectHandler(self):
1098 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1099 response doesn't make sense because the proxy server cannot request
1100 server authentication."""
1101
1102 if (self.path.find("www.server-auth.com") < 0):
1103 return False
1104
1105 challenge = 'Basic realm="WallyWorld"'
1106
1107 self.send_response(401) # unauthorized
1108 self.send_header('WWW-Authenticate', challenge)
1109 self.send_header('Connection', 'close')
1110 self.end_headers()
1111 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001112
1113 def DefaultConnectResponseHandler(self):
1114 """This is the catch-all response handler for CONNECT requests that aren't
1115 handled by one of the special handlers above. Real Web servers respond
1116 with 400 to CONNECT requests."""
1117
1118 contents = "Your client has issued a malformed or illegal request."
1119 self.send_response(400) # bad request
1120 self.send_header('Content-type', 'text/html')
1121 self.send_header("Content-Length", len(contents))
1122 self.end_headers()
1123 self.wfile.write(contents)
1124 return True
1125
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001126 def DeviceManagementHandler(self):
1127 """Delegates to the device management service used for cloud policy."""
1128 if not self._ShouldHandleRequest("/device_management"):
1129 return False
1130
1131 length = int(self.headers.getheader('content-length'))
1132 raw_request = self.rfile.read(length)
1133
1134 if not self.server._device_management_handler:
1135 import device_management
1136 policy_path = os.path.join(self.server.data_dir, 'device_management')
1137 self.server._device_management_handler = (
1138 device_management.TestServer(policy_path))
1139
1140 http_response, raw_reply = (
1141 self.server._device_management_handler.HandleRequest(self.path,
1142 self.headers,
1143 raw_request))
1144 self.send_response(http_response)
1145 self.end_headers()
1146 self.wfile.write(raw_reply)
1147 return True
1148
initial.commit94958cf2008-07-26 22:42:52 +00001149 # called by the redirect handling function when there is no parameter
1150 def sendRedirectHelp(self, redirect_name):
1151 self.send_response(200)
1152 self.send_header('Content-type', 'text/html')
1153 self.end_headers()
1154 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1155 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1156 self.wfile.write('</body></html>')
1157
akalin@chromium.org154bb132010-11-12 02:20:27 +00001158
1159class SyncPageHandler(BasePageHandler):
1160 """Handler for the main HTTP sync server."""
1161
1162 def __init__(self, request, client_address, sync_http_server):
1163 get_handlers = [self.ChromiumSyncTimeHandler]
1164 post_handlers = [self.ChromiumSyncCommandHandler]
1165 BasePageHandler.__init__(self, request, client_address,
1166 sync_http_server, [], get_handlers,
1167 post_handlers, [])
1168
1169 def ChromiumSyncTimeHandler(self):
1170 """Handle Chromium sync .../time requests.
1171
1172 The syncer sometimes checks server reachability by examining /time.
1173 """
1174 test_name = "/chromiumsync/time"
1175 if not self._ShouldHandleRequest(test_name):
1176 return False
1177
1178 self.send_response(200)
1179 self.send_header('Content-type', 'text/html')
1180 self.end_headers()
1181 return True
1182
1183 def ChromiumSyncCommandHandler(self):
1184 """Handle a chromiumsync command arriving via http.
1185
1186 This covers all sync protocol commands: authentication, getupdates, and
1187 commit.
1188 """
1189 test_name = "/chromiumsync/command"
1190 if not self._ShouldHandleRequest(test_name):
1191 return False
1192
1193 length = int(self.headers.getheader('content-length'))
1194 raw_request = self.rfile.read(length)
1195
1196 http_response, raw_reply = self.server.HandleCommand(
1197 self.path, raw_request)
1198 self.send_response(http_response)
1199 self.end_headers()
1200 self.wfile.write(raw_reply)
1201 return True
1202
1203
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001204def MakeDataDir():
1205 if options.data_dir:
1206 if not os.path.isdir(options.data_dir):
1207 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1208 return None
1209 my_data_dir = options.data_dir
1210 else:
1211 # Create the default path to our data dir, relative to the exe dir.
1212 my_data_dir = os.path.dirname(sys.argv[0])
1213 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001214 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001215
1216 #TODO(ibrar): Must use Find* funtion defined in google\tools
1217 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1218
1219 return my_data_dir
1220
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001221class FileMultiplexer:
1222 def __init__(self, fd1, fd2) :
1223 self.__fd1 = fd1
1224 self.__fd2 = fd2
1225
1226 def __del__(self) :
1227 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1228 self.__fd1.close()
1229 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1230 self.__fd2.close()
1231
1232 def write(self, text) :
1233 self.__fd1.write(text)
1234 self.__fd2.write(text)
1235
1236 def flush(self) :
1237 self.__fd1.flush()
1238 self.__fd2.flush()
1239
initial.commit94958cf2008-07-26 22:42:52 +00001240def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001241 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001242 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1243 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001244
1245 port = options.port
1246
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001247 if options.server_type == SERVER_HTTP:
1248 if options.cert:
1249 # let's make sure the cert file exists.
1250 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001251 print 'specified server cert file not found: ' + options.cert + \
1252 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001253 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001254 for ca_cert in options.ssl_client_ca:
1255 if not os.path.isfile(ca_cert):
1256 print 'specified trusted client CA file not found: ' + ca_cert + \
1257 ' exiting...'
1258 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001259 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001260 options.ssl_client_auth, options.ssl_client_ca,
1261 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001262 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001263 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001264 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001265 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001266
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001267 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001268 server.file_root_url = options.file_root_url
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001269 listen_port = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001270 server._device_management_handler = None
akalin@chromium.org154bb132010-11-12 02:20:27 +00001271 elif options.server_type == SERVER_SYNC:
1272 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1273 print 'Sync HTTP server started on port %d...' % server.server_port
1274 listen_port = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001275 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001276 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001277 my_data_dir = MakeDataDir()
1278
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001279 # Instantiate a dummy authorizer for managing 'virtual' users
1280 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1281
1282 # Define a new user having full r/w permissions and a read-only
1283 # anonymous user
1284 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1285
1286 authorizer.add_anonymous(my_data_dir)
1287
1288 # Instantiate FTP handler class
1289 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1290 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001291
1292 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001293 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1294 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001295
1296 # Instantiate FTP server class and listen to 127.0.0.1:port
1297 address = ('127.0.0.1', port)
1298 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001299 listen_port = server.socket.getsockname()[1]
1300 print 'FTP server started on port %d...' % listen_port
initial.commit94958cf2008-07-26 22:42:52 +00001301
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001302 # Notify the parent that we've started. (BaseServer subclasses
1303 # bind their sockets on construction.)
1304 if options.startup_pipe is not None:
1305 if sys.platform == 'win32':
1306 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1307 else:
1308 fd = options.startup_pipe
1309 startup_pipe = os.fdopen(fd, "w")
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001310 # Write the listening port as a 2 byte value. This is _not_ using
1311 # network byte ordering since the other end of the pipe is on the same
1312 # machine.
1313 startup_pipe.write(struct.pack('@H', listen_port))
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001314 startup_pipe.close()
1315
initial.commit94958cf2008-07-26 22:42:52 +00001316 try:
1317 server.serve_forever()
1318 except KeyboardInterrupt:
1319 print 'shutting down server'
1320 server.stop = True
1321
1322if __name__ == '__main__':
1323 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001324 option_parser.add_option("-f", '--ftp', action='store_const',
1325 const=SERVER_FTP, default=SERVER_HTTP,
1326 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001327 help='start up an FTP server.')
1328 option_parser.add_option('', '--sync', action='store_const',
1329 const=SERVER_SYNC, default=SERVER_HTTP,
1330 dest='server_type',
1331 help='start up a sync server.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001332 option_parser.add_option('', '--port', default='0', type='int',
1333 help='Port used by the server. If unspecified, the '
1334 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001335 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001336 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001337 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001338 help='Specify that https should be used, specify '
1339 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001340 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001341 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1342 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001343 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1344 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001345 'should include the CA named in the subject of '
1346 'the DER-encoded certificate contained in the '
1347 'specified file. This option may appear multiple '
1348 'times, indicating multiple CA names should be '
1349 'sent in the request.')
1350 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1351 help='Specify the bulk encryption algorithm(s)'
1352 'that will be accepted by the SSL server. Valid '
1353 'values are "aes256", "aes128", "3des", "rc4". If '
1354 'omitted, all algorithms will be used. This '
1355 'option may appear multiple times, indicating '
1356 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001357 option_parser.add_option('', '--file-root-url', default='/files/',
1358 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001359 option_parser.add_option('', '--startup-pipe', type='int',
1360 dest='startup_pipe',
1361 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001362 options, args = option_parser.parse_args()
1363
1364 sys.exit(main(options, args))