blob: 408e1e57f3115a2ca1bfb84151e319432eea7cf6 [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
initial.commit94958cf2008-07-26 22:42:52 +000049
50debug_output = sys.stderr
51def debug(str):
52 debug_output.write(str + "\n")
53 debug_output.flush()
54
55class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
56 """This is a specialization of of BaseHTTPServer to allow it
57 to be exited cleanly (by setting its "stop" member to True)."""
58
59 def serve_forever(self):
60 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000061 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000062 while not self.stop:
63 self.handle_request()
64 self.socket.close()
65
66class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
67 """This is a specialization of StoppableHTTPerver that add https support."""
68
davidben@chromium.org31282a12010-08-07 01:10:02 +000069 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000070 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000071 s = open(cert_path).read()
72 x509 = tlslite.api.X509()
73 x509.parse(s)
74 self.cert_chain = tlslite.api.X509CertChain([x509])
75 s = open(cert_path).read()
76 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000077 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000078 self.ssl_client_cas = []
79 for ca_file in ssl_client_cas:
80 s = open(ca_file).read()
81 x509 = tlslite.api.X509()
82 x509.parse(s)
83 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000084 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
85 if ssl_bulk_ciphers is not None:
86 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000087
88 self.session_cache = tlslite.api.SessionCache()
89 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
90
91 def handshake(self, tlsConnection):
92 """Creates the SSL connection."""
93 try:
94 tlsConnection.handshakeServer(certChain=self.cert_chain,
95 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000096 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000097 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000098 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000099 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000100 tlsConnection.ignoreAbruptClose = True
101 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000102 except tlslite.api.TLSAbruptCloseError:
103 # Ignore abrupt close.
104 return True
initial.commit94958cf2008-07-26 22:42:52 +0000105 except tlslite.api.TLSError, error:
106 print "Handshake failure:", str(error)
107 return False
108
109class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
110
111 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000112 self._connect_handlers = [
113 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000114 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000115 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000116 self._get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000117 self.NoCacheMaxAgeTimeHandler,
118 self.NoCacheTimeHandler,
119 self.CacheTimeHandler,
120 self.CacheExpiresHandler,
121 self.CacheProxyRevalidateHandler,
122 self.CachePrivateHandler,
123 self.CachePublicHandler,
124 self.CacheSMaxAgeHandler,
125 self.CacheMustRevalidateHandler,
126 self.CacheMustRevalidateMaxAgeHandler,
127 self.CacheNoStoreHandler,
128 self.CacheNoStoreMaxAgeHandler,
129 self.CacheNoTransformHandler,
130 self.DownloadHandler,
131 self.DownloadFinishHandler,
132 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000133 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000134 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000135 self.FileHandler,
136 self.RealFileWithCommonHeaderHandler,
137 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000138 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000139 self.AuthBasicHandler,
140 self.AuthDigestHandler,
141 self.SlowServerHandler,
142 self.ContentTypeHandler,
143 self.ServerRedirectHandler,
144 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000145 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000146 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000147 self.DefaultResponseHandler]
148 self._post_handlers = [
149 self.EchoTitleHandler,
150 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000151 self.ChromiumSyncCommandHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000152 self.EchoHandler,
153 self.DeviceManagementHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000154 self._put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000155 self.EchoTitleHandler,
156 self.EchoAllHandler,
157 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000158
maruel@google.come250a9b2009-03-10 17:39:46 +0000159 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000160 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000161 'gif': 'image/gif',
162 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000163 'jpg' : 'image/jpeg',
164 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000165 }
initial.commit94958cf2008-07-26 22:42:52 +0000166 self._default_mime_type = 'text/html'
167
maruel@google.come250a9b2009-03-10 17:39:46 +0000168 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
169 client_address,
170 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000171
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000172 def log_request(self, *args, **kwargs):
173 # Disable request logging to declutter test log output.
174 pass
175
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000176 def _ShouldHandleRequest(self, handler_name):
177 """Determines if the path can be handled by the handler.
178
179 We consider a handler valid if the path begins with the
180 handler name. It can optionally be followed by "?*", "/*".
181 """
182
183 pattern = re.compile('%s($|\?|/).*' % handler_name)
184 return pattern.match(self.path)
185
initial.commit94958cf2008-07-26 22:42:52 +0000186 def GetMIMETypeFromName(self, file_name):
187 """Returns the mime type for the specified file_name. So far it only looks
188 at the file extension."""
189
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000190 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000191 if len(extension) == 0:
192 # no extension.
193 return self._default_mime_type
194
ericroman@google.comc17ca532009-05-07 03:51:05 +0000195 # extension starts with a dot, so we need to remove it
196 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000197
initial.commit94958cf2008-07-26 22:42:52 +0000198 def NoCacheMaxAgeTimeHandler(self):
199 """This request handler yields a page with the title set to the current
200 system time, and no caching requested."""
201
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000202 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000203 return False
204
205 self.send_response(200)
206 self.send_header('Cache-Control', 'max-age=0')
207 self.send_header('Content-type', 'text/html')
208 self.end_headers()
209
maruel@google.come250a9b2009-03-10 17:39:46 +0000210 self.wfile.write('<html><head><title>%s</title></head></html>' %
211 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000212
213 return True
214
215 def NoCacheTimeHandler(self):
216 """This request handler yields a page with the title set to the current
217 system time, and no caching requested."""
218
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000219 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000220 return False
221
222 self.send_response(200)
223 self.send_header('Cache-Control', 'no-cache')
224 self.send_header('Content-type', 'text/html')
225 self.end_headers()
226
maruel@google.come250a9b2009-03-10 17:39:46 +0000227 self.wfile.write('<html><head><title>%s</title></head></html>' %
228 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000229
230 return True
231
232 def CacheTimeHandler(self):
233 """This request handler yields a page with the title set to the current
234 system time, and allows caching for one minute."""
235
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000236 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000237 return False
238
239 self.send_response(200)
240 self.send_header('Cache-Control', 'max-age=60')
241 self.send_header('Content-type', 'text/html')
242 self.end_headers()
243
maruel@google.come250a9b2009-03-10 17:39:46 +0000244 self.wfile.write('<html><head><title>%s</title></head></html>' %
245 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000246
247 return True
248
249 def CacheExpiresHandler(self):
250 """This request handler yields a page with the title set to the current
251 system time, and set the page to expire on 1 Jan 2099."""
252
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000253 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000254 return False
255
256 self.send_response(200)
257 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
258 self.send_header('Content-type', 'text/html')
259 self.end_headers()
260
maruel@google.come250a9b2009-03-10 17:39:46 +0000261 self.wfile.write('<html><head><title>%s</title></head></html>' %
262 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000263
264 return True
265
266 def CacheProxyRevalidateHandler(self):
267 """This request handler yields a page with the title set to the current
268 system time, and allows caching for 60 seconds"""
269
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000270 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000271 return False
272
273 self.send_response(200)
274 self.send_header('Content-type', 'text/html')
275 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
276 self.end_headers()
277
maruel@google.come250a9b2009-03-10 17:39:46 +0000278 self.wfile.write('<html><head><title>%s</title></head></html>' %
279 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000280
281 return True
282
283 def CachePrivateHandler(self):
284 """This request handler yields a page with the title set to the current
285 system time, and allows caching for 5 seconds."""
286
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000287 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000288 return False
289
290 self.send_response(200)
291 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000292 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000293 self.end_headers()
294
maruel@google.come250a9b2009-03-10 17:39:46 +0000295 self.wfile.write('<html><head><title>%s</title></head></html>' %
296 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000297
298 return True
299
300 def CachePublicHandler(self):
301 """This request handler yields a page with the title set to the current
302 system time, and allows caching for 5 seconds."""
303
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000304 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000305 return False
306
307 self.send_response(200)
308 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000309 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000310 self.end_headers()
311
maruel@google.come250a9b2009-03-10 17:39:46 +0000312 self.wfile.write('<html><head><title>%s</title></head></html>' %
313 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000314
315 return True
316
317 def CacheSMaxAgeHandler(self):
318 """This request handler yields a page with the title set to the current
319 system time, and does not allow for caching."""
320
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000321 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000322 return False
323
324 self.send_response(200)
325 self.send_header('Content-type', 'text/html')
326 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
327 self.end_headers()
328
maruel@google.come250a9b2009-03-10 17:39:46 +0000329 self.wfile.write('<html><head><title>%s</title></head></html>' %
330 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000331
332 return True
333
334 def CacheMustRevalidateHandler(self):
335 """This request handler yields a page with the title set to the current
336 system time, and does not allow caching."""
337
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000338 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000339 return False
340
341 self.send_response(200)
342 self.send_header('Content-type', 'text/html')
343 self.send_header('Cache-Control', 'must-revalidate')
344 self.end_headers()
345
maruel@google.come250a9b2009-03-10 17:39:46 +0000346 self.wfile.write('<html><head><title>%s</title></head></html>' %
347 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000348
349 return True
350
351 def CacheMustRevalidateMaxAgeHandler(self):
352 """This request handler yields a page with the title set to the current
353 system time, and does not allow caching event though max-age of 60
354 seconds is specified."""
355
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000356 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000357 return False
358
359 self.send_response(200)
360 self.send_header('Content-type', 'text/html')
361 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
362 self.end_headers()
363
maruel@google.come250a9b2009-03-10 17:39:46 +0000364 self.wfile.write('<html><head><title>%s</title></head></html>' %
365 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000366
367 return True
368
initial.commit94958cf2008-07-26 22:42:52 +0000369 def CacheNoStoreHandler(self):
370 """This request handler yields a page with the title set to the current
371 system time, and does not allow the page to be stored."""
372
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000373 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000374 return False
375
376 self.send_response(200)
377 self.send_header('Content-type', 'text/html')
378 self.send_header('Cache-Control', 'no-store')
379 self.end_headers()
380
maruel@google.come250a9b2009-03-10 17:39:46 +0000381 self.wfile.write('<html><head><title>%s</title></head></html>' %
382 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000383
384 return True
385
386 def CacheNoStoreMaxAgeHandler(self):
387 """This request handler yields a page with the title set to the current
388 system time, and does not allow the page to be stored even though max-age
389 of 60 seconds is specified."""
390
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000391 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000392 return False
393
394 self.send_response(200)
395 self.send_header('Content-type', 'text/html')
396 self.send_header('Cache-Control', 'max-age=60, no-store')
397 self.end_headers()
398
maruel@google.come250a9b2009-03-10 17:39:46 +0000399 self.wfile.write('<html><head><title>%s</title></head></html>' %
400 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000401
402 return True
403
404
405 def CacheNoTransformHandler(self):
406 """This request handler yields a page with the title set to the current
407 system time, and does not allow the content to transformed during
408 user-agent caching"""
409
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000410 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000411 return False
412
413 self.send_response(200)
414 self.send_header('Content-type', 'text/html')
415 self.send_header('Cache-Control', 'no-transform')
416 self.end_headers()
417
maruel@google.come250a9b2009-03-10 17:39:46 +0000418 self.wfile.write('<html><head><title>%s</title></head></html>' %
419 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000420
421 return True
422
423 def EchoHeader(self):
424 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000425 """The only difference between this function and the EchoHeaderOverride"""
426 """function is in the parameter being passed to the helper function"""
427 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000428
ananta@chromium.org219b2062009-10-23 16:09:41 +0000429 def EchoHeaderOverride(self):
430 """This handler echoes back the value of a specific request header."""
431 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
432 """IE to issue HTTP requests using the host network stack."""
433 """The Accept and Charset tests which expect the server to echo back"""
434 """the corresponding headers fail here as IE returns cached responses"""
435 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
436 """treats this request as a new request and does not cache it."""
437 return self.EchoHeaderHelper("/echoheaderoverride")
438
439 def EchoHeaderHelper(self, echo_header):
440 """This function echoes back the value of the request header passed in."""
441 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000442 return False
443
444 query_char = self.path.find('?')
445 if query_char != -1:
446 header_name = self.path[query_char+1:]
447
448 self.send_response(200)
449 self.send_header('Content-type', 'text/plain')
450 self.send_header('Cache-control', 'max-age=60000')
451 # insert a vary header to properly indicate that the cachability of this
452 # request is subject to value of the request header being echoed.
453 if len(header_name) > 0:
454 self.send_header('Vary', header_name)
455 self.end_headers()
456
457 if len(header_name) > 0:
458 self.wfile.write(self.headers.getheader(header_name))
459
460 return True
461
462 def EchoHandler(self):
463 """This handler just echoes back the payload of the request, for testing
464 form submission."""
465
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000466 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000467 return False
468
469 self.send_response(200)
470 self.send_header('Content-type', 'text/html')
471 self.end_headers()
472 length = int(self.headers.getheader('content-length'))
473 request = self.rfile.read(length)
474 self.wfile.write(request)
475 return True
476
477 def EchoTitleHandler(self):
478 """This handler is like Echo, but sets the page title to the request."""
479
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000480 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000481 return False
482
483 self.send_response(200)
484 self.send_header('Content-type', 'text/html')
485 self.end_headers()
486 length = int(self.headers.getheader('content-length'))
487 request = self.rfile.read(length)
488 self.wfile.write('<html><head><title>')
489 self.wfile.write(request)
490 self.wfile.write('</title></head></html>')
491 return True
492
493 def EchoAllHandler(self):
494 """This handler yields a (more) human-readable page listing information
495 about the request header & contents."""
496
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000497 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000498 return False
499
500 self.send_response(200)
501 self.send_header('Content-type', 'text/html')
502 self.end_headers()
503 self.wfile.write('<html><head><style>'
504 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
505 '</style></head><body>'
506 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000507 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000508 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000509
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000510 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000511 length = int(self.headers.getheader('content-length'))
512 qs = self.rfile.read(length)
513 params = cgi.parse_qs(qs, keep_blank_values=1)
514
515 for param in params:
516 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000517
518 self.wfile.write('</pre>')
519
520 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
521
522 self.wfile.write('</body></html>')
523 return True
524
525 def DownloadHandler(self):
526 """This handler sends a downloadable file with or without reporting
527 the size (6K)."""
528
529 if self.path.startswith("/download-unknown-size"):
530 send_length = False
531 elif self.path.startswith("/download-known-size"):
532 send_length = True
533 else:
534 return False
535
536 #
537 # The test which uses this functionality is attempting to send
538 # small chunks of data to the client. Use a fairly large buffer
539 # so that we'll fill chrome's IO buffer enough to force it to
540 # actually write the data.
541 # See also the comments in the client-side of this test in
542 # download_uitest.cc
543 #
544 size_chunk1 = 35*1024
545 size_chunk2 = 10*1024
546
547 self.send_response(200)
548 self.send_header('Content-type', 'application/octet-stream')
549 self.send_header('Cache-Control', 'max-age=0')
550 if send_length:
551 self.send_header('Content-Length', size_chunk1 + size_chunk2)
552 self.end_headers()
553
554 # First chunk of data:
555 self.wfile.write("*" * size_chunk1)
556 self.wfile.flush()
557
558 # handle requests until one of them clears this flag.
559 self.server.waitForDownload = True
560 while self.server.waitForDownload:
561 self.server.handle_request()
562
563 # Second chunk of data:
564 self.wfile.write("*" * size_chunk2)
565 return True
566
567 def DownloadFinishHandler(self):
568 """This handler just tells the server to finish the current download."""
569
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000570 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000571 return False
572
573 self.server.waitForDownload = False
574 self.send_response(200)
575 self.send_header('Content-type', 'text/html')
576 self.send_header('Cache-Control', 'max-age=0')
577 self.end_headers()
578 return True
579
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000580 def _ReplaceFileData(self, data, query_parameters):
581 """Replaces matching substrings in a file.
582
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000583 If the 'replace_text' URL query parameter is present, it is expected to be
584 of the form old_text:new_text, which indicates that any old_text strings in
585 the file are replaced with new_text. Multiple 'replace_text' parameters may
586 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000587
588 If the parameters are not present, |data| is returned.
589 """
590 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000591 replace_text_values = query_dict.get('replace_text', [])
592 for replace_text_value in replace_text_values:
593 replace_text_args = replace_text_value.split(':')
594 if len(replace_text_args) != 2:
595 raise ValueError(
596 'replace_text must be of form old_text:new_text. Actual value: %s' %
597 replace_text_value)
598 old_text_b64, new_text_b64 = replace_text_args
599 old_text = base64.urlsafe_b64decode(old_text_b64)
600 new_text = base64.urlsafe_b64decode(new_text_b64)
601 data = data.replace(old_text, new_text)
602 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000603
initial.commit94958cf2008-07-26 22:42:52 +0000604 def FileHandler(self):
605 """This handler sends the contents of the requested file. Wow, it's like
606 a real webserver!"""
607
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000608 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000609 if not self.path.startswith(prefix):
610 return False
611
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000612 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000613 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000614 self.rfile.read(int(self.headers.getheader('content-length')))
615
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000616 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
617 sub_path = url_path[len(prefix):]
618 entries = sub_path.split('/')
619 file_path = os.path.join(self.server.data_dir, *entries)
620 if os.path.isdir(file_path):
621 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000622
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000623 if not os.path.isfile(file_path):
624 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000625 self.send_error(404)
626 return True
627
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000628 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000629 data = f.read()
630 f.close()
631
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000632 data = self._ReplaceFileData(data, query)
633
initial.commit94958cf2008-07-26 22:42:52 +0000634 # If file.mock-http-headers exists, it contains the headers we
635 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000636 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000637 if os.path.isfile(headers_path):
638 f = open(headers_path, "r")
639
640 # "HTTP/1.1 200 OK"
641 response = f.readline()
642 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
643 self.send_response(int(status_code))
644
645 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000646 header_values = re.findall('(\S+):\s*(.*)', line)
647 if len(header_values) > 0:
648 # "name: value"
649 name, value = header_values[0]
650 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000651 f.close()
652 else:
653 # Could be more generic once we support mime-type sniffing, but for
654 # now we need to set it explicitly.
655 self.send_response(200)
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000656 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
initial.commit94958cf2008-07-26 22:42:52 +0000657 self.send_header('Content-Length', len(data))
658 self.end_headers()
659
660 self.wfile.write(data)
661
662 return True
663
664 def RealFileWithCommonHeaderHandler(self):
665 """This handler sends the contents of the requested file without the pseudo
666 http head!"""
667
668 prefix='/realfiles/'
669 if not self.path.startswith(prefix):
670 return False
671
672 file = self.path[len(prefix):]
673 path = os.path.join(self.server.data_dir, file)
674
675 try:
676 f = open(path, "rb")
677 data = f.read()
678 f.close()
679
680 # just simply set the MIME as octal stream
681 self.send_response(200)
682 self.send_header('Content-type', 'application/octet-stream')
683 self.end_headers()
684
685 self.wfile.write(data)
686 except:
687 self.send_error(404)
688
689 return True
690
691 def RealBZ2FileWithCommonHeaderHandler(self):
692 """This handler sends the bzip2 contents of the requested file with
693 corresponding Content-Encoding field in http head!"""
694
695 prefix='/realbz2files/'
696 if not self.path.startswith(prefix):
697 return False
698
699 parts = self.path.split('?')
700 file = parts[0][len(prefix):]
701 path = os.path.join(self.server.data_dir, file) + '.bz2'
702
703 if len(parts) > 1:
704 options = parts[1]
705 else:
706 options = ''
707
708 try:
709 self.send_response(200)
710 accept_encoding = self.headers.get("Accept-Encoding")
711 if accept_encoding.find("bzip2") != -1:
712 f = open(path, "rb")
713 data = f.read()
714 f.close()
715 self.send_header('Content-Encoding', 'bzip2')
716 self.send_header('Content-type', 'application/x-bzip2')
717 self.end_headers()
718 if options == 'incremental-header':
719 self.wfile.write(data[:1])
720 self.wfile.flush()
721 time.sleep(1.0)
722 self.wfile.write(data[1:])
723 else:
724 self.wfile.write(data)
725 else:
726 """client do not support bzip2 format, send pseudo content
727 """
728 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
729 self.end_headers()
730 self.wfile.write("you do not support bzip2 encoding")
731 except:
732 self.send_error(404)
733
734 return True
735
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000736 def SetCookieHandler(self):
737 """This handler just sets a cookie, for testing cookie handling."""
738
739 if not self._ShouldHandleRequest("/set-cookie"):
740 return False
741
742 query_char = self.path.find('?')
743 if query_char != -1:
744 cookie_values = self.path[query_char + 1:].split('&')
745 else:
746 cookie_values = ("",)
747 self.send_response(200)
748 self.send_header('Content-type', 'text/html')
749 for cookie_value in cookie_values:
750 self.send_header('Set-Cookie', '%s' % cookie_value)
751 self.end_headers()
752 for cookie_value in cookie_values:
753 self.wfile.write('%s' % cookie_value)
754 return True
755
initial.commit94958cf2008-07-26 22:42:52 +0000756 def AuthBasicHandler(self):
757 """This handler tests 'Basic' authentication. It just sends a page with
758 title 'user/pass' if you succeed."""
759
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000760 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000761 return False
762
763 username = userpass = password = b64str = ""
764
ericroman@google.com239b4d82009-03-27 04:00:22 +0000765 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
766
initial.commit94958cf2008-07-26 22:42:52 +0000767 auth = self.headers.getheader('authorization')
768 try:
769 if not auth:
770 raise Exception('no auth')
771 b64str = re.findall(r'Basic (\S+)', auth)[0]
772 userpass = base64.b64decode(b64str)
773 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
774 if password != 'secret':
775 raise Exception('wrong password')
776 except Exception, e:
777 # Authentication failed.
778 self.send_response(401)
779 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
780 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000781 if set_cookie_if_challenged:
782 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000783 self.end_headers()
784 self.wfile.write('<html><head>')
785 self.wfile.write('<title>Denied: %s</title>' % e)
786 self.wfile.write('</head><body>')
787 self.wfile.write('auth=%s<p>' % auth)
788 self.wfile.write('b64str=%s<p>' % b64str)
789 self.wfile.write('username: %s<p>' % username)
790 self.wfile.write('userpass: %s<p>' % userpass)
791 self.wfile.write('password: %s<p>' % password)
792 self.wfile.write('You sent:<br>%s<p>' % self.headers)
793 self.wfile.write('</body></html>')
794 return True
795
796 # Authentication successful. (Return a cachable response to allow for
797 # testing cached pages that require authentication.)
798 if_none_match = self.headers.getheader('if-none-match')
799 if if_none_match == "abc":
800 self.send_response(304)
801 self.end_headers()
802 else:
803 self.send_response(200)
804 self.send_header('Content-type', 'text/html')
805 self.send_header('Cache-control', 'max-age=60000')
806 self.send_header('Etag', 'abc')
807 self.end_headers()
808 self.wfile.write('<html><head>')
809 self.wfile.write('<title>%s/%s</title>' % (username, password))
810 self.wfile.write('</head><body>')
811 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000812 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000813 self.wfile.write('</body></html>')
814
815 return True
816
tonyg@chromium.org75054202010-03-31 22:06:10 +0000817 def GetNonce(self, force_reset=False):
818 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000819
tonyg@chromium.org75054202010-03-31 22:06:10 +0000820 This is a fake implementation. A real implementation would only use a given
821 nonce a single time (hence the name n-once). However, for the purposes of
822 unittesting, we don't care about the security of the nonce.
823
824 Args:
825 force_reset: Iff set, the nonce will be changed. Useful for testing the
826 "stale" response.
827 """
828 if force_reset or not self.server.nonce_time:
829 self.server.nonce_time = time.time()
830 return _new_md5('privatekey%s%d' %
831 (self.path, self.server.nonce_time)).hexdigest()
832
833 def AuthDigestHandler(self):
834 """This handler tests 'Digest' authentication.
835
836 It just sends a page with title 'user/pass' if you succeed.
837
838 A stale response is sent iff "stale" is present in the request path.
839 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000840 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000841 return False
842
tonyg@chromium.org75054202010-03-31 22:06:10 +0000843 stale = 'stale' in self.path
844 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000845 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000846 password = 'secret'
847 realm = 'testrealm'
848
849 auth = self.headers.getheader('authorization')
850 pairs = {}
851 try:
852 if not auth:
853 raise Exception('no auth')
854 if not auth.startswith('Digest'):
855 raise Exception('not digest')
856 # Pull out all the name="value" pairs as a dictionary.
857 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
858
859 # Make sure it's all valid.
860 if pairs['nonce'] != nonce:
861 raise Exception('wrong nonce')
862 if pairs['opaque'] != opaque:
863 raise Exception('wrong opaque')
864
865 # Check the 'response' value and make sure it matches our magic hash.
866 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000867 hash_a1 = _new_md5(
868 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000869 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000870 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000871 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000872 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
873 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000874 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000875
876 if pairs['response'] != response:
877 raise Exception('wrong password')
878 except Exception, e:
879 # Authentication failed.
880 self.send_response(401)
881 hdr = ('Digest '
882 'realm="%s", '
883 'domain="/", '
884 'qop="auth", '
885 'algorithm=MD5, '
886 'nonce="%s", '
887 'opaque="%s"') % (realm, nonce, opaque)
888 if stale:
889 hdr += ', stale="TRUE"'
890 self.send_header('WWW-Authenticate', hdr)
891 self.send_header('Content-type', 'text/html')
892 self.end_headers()
893 self.wfile.write('<html><head>')
894 self.wfile.write('<title>Denied: %s</title>' % e)
895 self.wfile.write('</head><body>')
896 self.wfile.write('auth=%s<p>' % auth)
897 self.wfile.write('pairs=%s<p>' % pairs)
898 self.wfile.write('You sent:<br>%s<p>' % self.headers)
899 self.wfile.write('We are replying:<br>%s<p>' % hdr)
900 self.wfile.write('</body></html>')
901 return True
902
903 # Authentication successful.
904 self.send_response(200)
905 self.send_header('Content-type', 'text/html')
906 self.end_headers()
907 self.wfile.write('<html><head>')
908 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
909 self.wfile.write('</head><body>')
910 self.wfile.write('auth=%s<p>' % auth)
911 self.wfile.write('pairs=%s<p>' % pairs)
912 self.wfile.write('</body></html>')
913
914 return True
915
916 def SlowServerHandler(self):
917 """Wait for the user suggested time before responding. The syntax is
918 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000919 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000920 return False
921 query_char = self.path.find('?')
922 wait_sec = 1.0
923 if query_char >= 0:
924 try:
925 wait_sec = int(self.path[query_char + 1:])
926 except ValueError:
927 pass
928 time.sleep(wait_sec)
929 self.send_response(200)
930 self.send_header('Content-type', 'text/plain')
931 self.end_headers()
932 self.wfile.write("waited %d seconds" % wait_sec)
933 return True
934
935 def ContentTypeHandler(self):
936 """Returns a string of html with the given content type. E.g.,
937 /contenttype?text/css returns an html file with the Content-Type
938 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000939 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000940 return False
941 query_char = self.path.find('?')
942 content_type = self.path[query_char + 1:].strip()
943 if not content_type:
944 content_type = 'text/html'
945 self.send_response(200)
946 self.send_header('Content-Type', content_type)
947 self.end_headers()
948 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
949 return True
950
951 def ServerRedirectHandler(self):
952 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000953 '/server-redirect?http://foo.bar/asdf' to redirect to
954 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000955
956 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000957 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000958 return False
959
960 query_char = self.path.find('?')
961 if query_char < 0 or len(self.path) <= query_char + 1:
962 self.sendRedirectHelp(test_name)
963 return True
964 dest = self.path[query_char + 1:]
965
966 self.send_response(301) # moved permanently
967 self.send_header('Location', dest)
968 self.send_header('Content-type', 'text/html')
969 self.end_headers()
970 self.wfile.write('<html><head>')
971 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
972
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000973 return True
initial.commit94958cf2008-07-26 22:42:52 +0000974
975 def ClientRedirectHandler(self):
976 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000977 '/client-redirect?http://foo.bar/asdf' to redirect to
978 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000979
980 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000981 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000982 return False
983
984 query_char = self.path.find('?');
985 if query_char < 0 or len(self.path) <= query_char + 1:
986 self.sendRedirectHelp(test_name)
987 return True
988 dest = self.path[query_char + 1:]
989
990 self.send_response(200)
991 self.send_header('Content-type', 'text/html')
992 self.end_headers()
993 self.wfile.write('<html><head>')
994 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
995 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
996
997 return True
998
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000999 def ChromiumSyncTimeHandler(self):
1000 """Handle Chromium sync .../time requests.
1001
1002 The syncer sometimes checks server reachability by examining /time.
1003 """
1004 test_name = "/chromiumsync/time"
1005 if not self._ShouldHandleRequest(test_name):
1006 return False
1007
1008 self.send_response(200)
1009 self.send_header('Content-type', 'text/html')
1010 self.end_headers()
1011 return True
1012
1013 def ChromiumSyncCommandHandler(self):
1014 """Handle a chromiumsync command arriving via http.
1015
1016 This covers all sync protocol commands: authentication, getupdates, and
1017 commit.
1018 """
1019 test_name = "/chromiumsync/command"
1020 if not self._ShouldHandleRequest(test_name):
1021 return False
1022
1023 length = int(self.headers.getheader('content-length'))
1024 raw_request = self.rfile.read(length)
1025
pathorn@chromium.org44920122010-07-27 18:25:35 +00001026 if not self.server._sync_handler:
1027 import chromiumsync
1028 self.server._sync_handler = chromiumsync.TestServer()
1029 http_response, raw_reply = self.server._sync_handler.HandleCommand(
nick@chromium.org06b8a662010-09-22 22:50:18 +00001030 self.path, raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001031 self.send_response(http_response)
1032 self.end_headers()
1033 self.wfile.write(raw_reply)
1034 return True
1035
tony@chromium.org03266982010-03-05 03:18:42 +00001036 def MultipartHandler(self):
1037 """Send a multipart response (10 text/html pages)."""
1038 test_name = "/multipart"
1039 if not self._ShouldHandleRequest(test_name):
1040 return False
1041
1042 num_frames = 10
1043 bound = '12345'
1044 self.send_response(200)
1045 self.send_header('Content-type',
1046 'multipart/x-mixed-replace;boundary=' + bound)
1047 self.end_headers()
1048
1049 for i in xrange(num_frames):
1050 self.wfile.write('--' + bound + '\r\n')
1051 self.wfile.write('Content-type: text/html\r\n\r\n')
1052 self.wfile.write('<title>page ' + str(i) + '</title>')
1053 self.wfile.write('page ' + str(i))
1054
1055 self.wfile.write('--' + bound + '--')
1056 return True
1057
initial.commit94958cf2008-07-26 22:42:52 +00001058 def DefaultResponseHandler(self):
1059 """This is the catch-all response handler for requests that aren't handled
1060 by one of the special handlers above.
1061 Note that we specify the content-length as without it the https connection
1062 is not closed properly (and the browser keeps expecting data)."""
1063
1064 contents = "Default response given for path: " + self.path
1065 self.send_response(200)
1066 self.send_header('Content-type', 'text/html')
1067 self.send_header("Content-Length", len(contents))
1068 self.end_headers()
1069 self.wfile.write(contents)
1070 return True
1071
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001072 def RedirectConnectHandler(self):
1073 """Sends a redirect to the CONNECT request for www.redirect.com. This
1074 response is not specified by the RFC, so the browser should not follow
1075 the redirect."""
1076
1077 if (self.path.find("www.redirect.com") < 0):
1078 return False
1079
1080 dest = "http://www.destination.com/foo.js"
1081
1082 self.send_response(302) # moved temporarily
1083 self.send_header('Location', dest)
1084 self.send_header('Connection', 'close')
1085 self.end_headers()
1086 return True
1087
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001088 def ServerAuthConnectHandler(self):
1089 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1090 response doesn't make sense because the proxy server cannot request
1091 server authentication."""
1092
1093 if (self.path.find("www.server-auth.com") < 0):
1094 return False
1095
1096 challenge = 'Basic realm="WallyWorld"'
1097
1098 self.send_response(401) # unauthorized
1099 self.send_header('WWW-Authenticate', challenge)
1100 self.send_header('Connection', 'close')
1101 self.end_headers()
1102 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001103
1104 def DefaultConnectResponseHandler(self):
1105 """This is the catch-all response handler for CONNECT requests that aren't
1106 handled by one of the special handlers above. Real Web servers respond
1107 with 400 to CONNECT requests."""
1108
1109 contents = "Your client has issued a malformed or illegal request."
1110 self.send_response(400) # bad request
1111 self.send_header('Content-type', 'text/html')
1112 self.send_header("Content-Length", len(contents))
1113 self.end_headers()
1114 self.wfile.write(contents)
1115 return True
1116
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001117 def DeviceManagementHandler(self):
1118 """Delegates to the device management service used for cloud policy."""
1119 if not self._ShouldHandleRequest("/device_management"):
1120 return False
1121
1122 length = int(self.headers.getheader('content-length'))
1123 raw_request = self.rfile.read(length)
1124
1125 if not self.server._device_management_handler:
1126 import device_management
1127 policy_path = os.path.join(self.server.data_dir, 'device_management')
1128 self.server._device_management_handler = (
1129 device_management.TestServer(policy_path))
1130
1131 http_response, raw_reply = (
1132 self.server._device_management_handler.HandleRequest(self.path,
1133 self.headers,
1134 raw_request))
1135 self.send_response(http_response)
1136 self.end_headers()
1137 self.wfile.write(raw_reply)
1138 return True
1139
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001140 def do_CONNECT(self):
1141 for handler in self._connect_handlers:
1142 if handler():
1143 return
1144
initial.commit94958cf2008-07-26 22:42:52 +00001145 def do_GET(self):
1146 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001147 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001148 return
1149
1150 def do_POST(self):
1151 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001152 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001153 return
1154
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001155 def do_PUT(self):
1156 for handler in self._put_handlers:
1157 if handler():
1158 return
1159
initial.commit94958cf2008-07-26 22:42:52 +00001160 # called by the redirect handling function when there is no parameter
1161 def sendRedirectHelp(self, redirect_name):
1162 self.send_response(200)
1163 self.send_header('Content-type', 'text/html')
1164 self.end_headers()
1165 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1166 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1167 self.wfile.write('</body></html>')
1168
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001169def MakeDataDir():
1170 if options.data_dir:
1171 if not os.path.isdir(options.data_dir):
1172 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1173 return None
1174 my_data_dir = options.data_dir
1175 else:
1176 # Create the default path to our data dir, relative to the exe dir.
1177 my_data_dir = os.path.dirname(sys.argv[0])
1178 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001179 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001180
1181 #TODO(ibrar): Must use Find* funtion defined in google\tools
1182 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1183
1184 return my_data_dir
1185
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001186class FileMultiplexer:
1187 def __init__(self, fd1, fd2) :
1188 self.__fd1 = fd1
1189 self.__fd2 = fd2
1190
1191 def __del__(self) :
1192 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1193 self.__fd1.close()
1194 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1195 self.__fd2.close()
1196
1197 def write(self, text) :
1198 self.__fd1.write(text)
1199 self.__fd2.write(text)
1200
1201 def flush(self) :
1202 self.__fd1.flush()
1203 self.__fd2.flush()
1204
initial.commit94958cf2008-07-26 22:42:52 +00001205def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001206 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001207 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1208 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001209
1210 port = options.port
1211
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001212 if options.server_type == SERVER_HTTP:
1213 if options.cert:
1214 # let's make sure the cert file exists.
1215 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001216 print 'specified server cert file not found: ' + options.cert + \
1217 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001218 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001219 for ca_cert in options.ssl_client_ca:
1220 if not os.path.isfile(ca_cert):
1221 print 'specified trusted client CA file not found: ' + ca_cert + \
1222 ' exiting...'
1223 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001224 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001225 options.ssl_client_auth, options.ssl_client_ca,
1226 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001227 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001228 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001229 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001230 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001231
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001232 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001233 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001234 server._sync_handler = None
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001235 listen_port = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001236 server._device_management_handler = None
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001237 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001238 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001239 my_data_dir = MakeDataDir()
1240
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001241 # Instantiate a dummy authorizer for managing 'virtual' users
1242 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1243
1244 # Define a new user having full r/w permissions and a read-only
1245 # anonymous user
1246 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1247
1248 authorizer.add_anonymous(my_data_dir)
1249
1250 # Instantiate FTP handler class
1251 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1252 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001253
1254 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001255 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1256 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001257
1258 # Instantiate FTP server class and listen to 127.0.0.1:port
1259 address = ('127.0.0.1', port)
1260 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001261 listen_port = server.socket.getsockname()[1]
1262 print 'FTP server started on port %d...' % listen_port
initial.commit94958cf2008-07-26 22:42:52 +00001263
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001264 # Notify the parent that we've started. (BaseServer subclasses
1265 # bind their sockets on construction.)
1266 if options.startup_pipe is not None:
1267 if sys.platform == 'win32':
1268 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1269 else:
1270 fd = options.startup_pipe
1271 startup_pipe = os.fdopen(fd, "w")
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001272 # Write the listening port as a 2 byte value. This is _not_ using
1273 # network byte ordering since the other end of the pipe is on the same
1274 # machine.
1275 startup_pipe.write(struct.pack('@H', listen_port))
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001276 startup_pipe.close()
1277
initial.commit94958cf2008-07-26 22:42:52 +00001278 try:
1279 server.serve_forever()
1280 except KeyboardInterrupt:
1281 print 'shutting down server'
1282 server.stop = True
1283
1284if __name__ == '__main__':
1285 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001286 option_parser.add_option("-f", '--ftp', action='store_const',
1287 const=SERVER_FTP, default=SERVER_HTTP,
1288 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001289 help='FTP or HTTP server: default is HTTP.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001290 option_parser.add_option('', '--port', default='0', type='int',
1291 help='Port used by the server. If unspecified, the '
1292 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001293 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001294 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001295 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001296 help='Specify that https should be used, specify '
1297 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001298 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001299 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1300 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001301 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1302 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001303 'should include the CA named in the subject of '
1304 'the DER-encoded certificate contained in the '
1305 'specified file. This option may appear multiple '
1306 'times, indicating multiple CA names should be '
1307 'sent in the request.')
1308 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1309 help='Specify the bulk encryption algorithm(s)'
1310 'that will be accepted by the SSL server. Valid '
1311 'values are "aes256", "aes128", "3des", "rc4". If '
1312 'omitted, all algorithms will be used. This '
1313 'option may appear multiple times, indicating '
1314 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001315 option_parser.add_option('', '--file-root-url', default='/files/',
1316 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001317 option_parser.add_option('', '--startup-pipe', type='int',
1318 dest='startup_pipe',
1319 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001320 options, args = option_parser.parse_args()
1321
1322 sys.exit(main(options, args))