blob: 4f30b2c37592e431da7e4f7d3338c6a42eda0697 [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.
9It defaults to living on localhost:8888.
10It can use https if you specify the flag --https=CERT where CERT is the path
11to a pem file containing the certificate and private key that should be used.
12To shut it down properly, visit localhost:8888/kill.
13"""
14
15import base64
16import BaseHTTPServer
17import cgi
initial.commit94958cf2008-07-26 22:42:52 +000018import optparse
19import os
20import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000021import shutil
initial.commit94958cf2008-07-26 22:42:52 +000022import SocketServer
23import sys
24import time
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000025import urllib2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000026import warnings
27
28# Ignore deprecation warnings, they make our output more cluttered.
29warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000030
31import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000032import tlslite
33import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000034
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000035try:
36 import hashlib
37 _new_md5 = hashlib.md5
38except ImportError:
39 import md5
40 _new_md5 = md5.new
41
maruel@chromium.org756cf982009-03-05 12:46:38 +000042SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000043SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000044
45debug_output = sys.stderr
46def debug(str):
47 debug_output.write(str + "\n")
48 debug_output.flush()
49
50class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
51 """This is a specialization of of BaseHTTPServer to allow it
52 to be exited cleanly (by setting its "stop" member to True)."""
53
54 def serve_forever(self):
55 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000056 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000057 while not self.stop:
58 self.handle_request()
59 self.socket.close()
60
61class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
62 """This is a specialization of StoppableHTTPerver that add https support."""
63
davidben@chromium.org31282a12010-08-07 01:10:02 +000064 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000065 ssl_client_auth, ssl_client_cas):
initial.commit94958cf2008-07-26 22:42:52 +000066 s = open(cert_path).read()
67 x509 = tlslite.api.X509()
68 x509.parse(s)
69 self.cert_chain = tlslite.api.X509CertChain([x509])
70 s = open(cert_path).read()
71 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000072 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000073 self.ssl_client_cas = []
74 for ca_file in ssl_client_cas:
75 s = open(ca_file).read()
76 x509 = tlslite.api.X509()
77 x509.parse(s)
78 self.ssl_client_cas.append(x509.subject)
initial.commit94958cf2008-07-26 22:42:52 +000079
80 self.session_cache = tlslite.api.SessionCache()
81 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
82
83 def handshake(self, tlsConnection):
84 """Creates the SSL connection."""
85 try:
86 tlsConnection.handshakeServer(certChain=self.cert_chain,
87 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000088 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000089 reqCert=self.ssl_client_auth,
90 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +000091 tlsConnection.ignoreAbruptClose = True
92 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000093 except tlslite.api.TLSAbruptCloseError:
94 # Ignore abrupt close.
95 return True
initial.commit94958cf2008-07-26 22:42:52 +000096 except tlslite.api.TLSError, error:
97 print "Handshake failure:", str(error)
98 return False
99
100class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
101
102 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000103 self._connect_handlers = [
104 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000105 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000106 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000107 self._get_handlers = [
108 self.KillHandler,
109 self.NoCacheMaxAgeTimeHandler,
110 self.NoCacheTimeHandler,
111 self.CacheTimeHandler,
112 self.CacheExpiresHandler,
113 self.CacheProxyRevalidateHandler,
114 self.CachePrivateHandler,
115 self.CachePublicHandler,
116 self.CacheSMaxAgeHandler,
117 self.CacheMustRevalidateHandler,
118 self.CacheMustRevalidateMaxAgeHandler,
119 self.CacheNoStoreHandler,
120 self.CacheNoStoreMaxAgeHandler,
121 self.CacheNoTransformHandler,
122 self.DownloadHandler,
123 self.DownloadFinishHandler,
124 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000125 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000126 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000127 self.FileHandler,
128 self.RealFileWithCommonHeaderHandler,
129 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000130 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000131 self.AuthBasicHandler,
132 self.AuthDigestHandler,
133 self.SlowServerHandler,
134 self.ContentTypeHandler,
135 self.ServerRedirectHandler,
136 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000137 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000138 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000139 self.DefaultResponseHandler]
140 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000141 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000142 self.EchoTitleHandler,
143 self.EchoAllHandler,
skrul@chromium.orgfff501f2010-08-13 21:01:52 +0000144 self.ChromiumSyncConfigureHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000145 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000146 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000147 self._put_handlers = [
148 self.WriteFile,
149 self.EchoTitleHandler,
150 self.EchoAllHandler,
151 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000152
maruel@google.come250a9b2009-03-10 17:39:46 +0000153 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000154 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000155 'gif': 'image/gif',
156 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000157 'jpg' : 'image/jpeg',
158 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000159 }
initial.commit94958cf2008-07-26 22:42:52 +0000160 self._default_mime_type = 'text/html'
161
maruel@google.come250a9b2009-03-10 17:39:46 +0000162 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
163 client_address,
164 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000165
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000166 def log_request(self, *args, **kwargs):
167 # Disable request logging to declutter test log output.
168 pass
169
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000170 def _ShouldHandleRequest(self, handler_name):
171 """Determines if the path can be handled by the handler.
172
173 We consider a handler valid if the path begins with the
174 handler name. It can optionally be followed by "?*", "/*".
175 """
176
177 pattern = re.compile('%s($|\?|/).*' % handler_name)
178 return pattern.match(self.path)
179
initial.commit94958cf2008-07-26 22:42:52 +0000180 def GetMIMETypeFromName(self, file_name):
181 """Returns the mime type for the specified file_name. So far it only looks
182 at the file extension."""
183
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000184 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000185 if len(extension) == 0:
186 # no extension.
187 return self._default_mime_type
188
ericroman@google.comc17ca532009-05-07 03:51:05 +0000189 # extension starts with a dot, so we need to remove it
190 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000191
192 def KillHandler(self):
193 """This request handler kills the server, for use when we're done"
194 with the a particular test."""
195
196 if (self.path.find("kill") < 0):
197 return False
198
199 self.send_response(200)
200 self.send_header('Content-type', 'text/html')
201 self.send_header('Cache-Control', 'max-age=0')
202 self.end_headers()
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000203 if options.never_die:
204 self.wfile.write('I cannot die!! BWAHAHA')
205 else:
206 self.wfile.write('Goodbye cruel world!')
207 self.server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +0000208
209 return True
210
211 def NoCacheMaxAgeTimeHandler(self):
212 """This request handler yields a page with the title set to the current
213 system time, and no caching requested."""
214
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000215 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000216 return False
217
218 self.send_response(200)
219 self.send_header('Cache-Control', 'max-age=0')
220 self.send_header('Content-type', 'text/html')
221 self.end_headers()
222
maruel@google.come250a9b2009-03-10 17:39:46 +0000223 self.wfile.write('<html><head><title>%s</title></head></html>' %
224 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000225
226 return True
227
228 def NoCacheTimeHandler(self):
229 """This request handler yields a page with the title set to the current
230 system time, and no caching requested."""
231
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000232 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000233 return False
234
235 self.send_response(200)
236 self.send_header('Cache-Control', 'no-cache')
237 self.send_header('Content-type', 'text/html')
238 self.end_headers()
239
maruel@google.come250a9b2009-03-10 17:39:46 +0000240 self.wfile.write('<html><head><title>%s</title></head></html>' %
241 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000242
243 return True
244
245 def CacheTimeHandler(self):
246 """This request handler yields a page with the title set to the current
247 system time, and allows caching for one minute."""
248
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000249 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000250 return False
251
252 self.send_response(200)
253 self.send_header('Cache-Control', 'max-age=60')
254 self.send_header('Content-type', 'text/html')
255 self.end_headers()
256
maruel@google.come250a9b2009-03-10 17:39:46 +0000257 self.wfile.write('<html><head><title>%s</title></head></html>' %
258 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000259
260 return True
261
262 def CacheExpiresHandler(self):
263 """This request handler yields a page with the title set to the current
264 system time, and set the page to expire on 1 Jan 2099."""
265
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000266 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000267 return False
268
269 self.send_response(200)
270 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
271 self.send_header('Content-type', 'text/html')
272 self.end_headers()
273
maruel@google.come250a9b2009-03-10 17:39:46 +0000274 self.wfile.write('<html><head><title>%s</title></head></html>' %
275 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000276
277 return True
278
279 def CacheProxyRevalidateHandler(self):
280 """This request handler yields a page with the title set to the current
281 system time, and allows caching for 60 seconds"""
282
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000283 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000284 return False
285
286 self.send_response(200)
287 self.send_header('Content-type', 'text/html')
288 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
289 self.end_headers()
290
maruel@google.come250a9b2009-03-10 17:39:46 +0000291 self.wfile.write('<html><head><title>%s</title></head></html>' %
292 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000293
294 return True
295
296 def CachePrivateHandler(self):
297 """This request handler yields a page with the title set to the current
298 system time, and allows caching for 5 seconds."""
299
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000300 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000301 return False
302
303 self.send_response(200)
304 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000305 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000306 self.end_headers()
307
maruel@google.come250a9b2009-03-10 17:39:46 +0000308 self.wfile.write('<html><head><title>%s</title></head></html>' %
309 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000310
311 return True
312
313 def CachePublicHandler(self):
314 """This request handler yields a page with the title set to the current
315 system time, and allows caching for 5 seconds."""
316
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000317 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000318 return False
319
320 self.send_response(200)
321 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000322 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000323 self.end_headers()
324
maruel@google.come250a9b2009-03-10 17:39:46 +0000325 self.wfile.write('<html><head><title>%s</title></head></html>' %
326 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000327
328 return True
329
330 def CacheSMaxAgeHandler(self):
331 """This request handler yields a page with the title set to the current
332 system time, and does not allow for caching."""
333
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000334 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000335 return False
336
337 self.send_response(200)
338 self.send_header('Content-type', 'text/html')
339 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
340 self.end_headers()
341
maruel@google.come250a9b2009-03-10 17:39:46 +0000342 self.wfile.write('<html><head><title>%s</title></head></html>' %
343 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000344
345 return True
346
347 def CacheMustRevalidateHandler(self):
348 """This request handler yields a page with the title set to the current
349 system time, and does not allow caching."""
350
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000351 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000352 return False
353
354 self.send_response(200)
355 self.send_header('Content-type', 'text/html')
356 self.send_header('Cache-Control', 'must-revalidate')
357 self.end_headers()
358
maruel@google.come250a9b2009-03-10 17:39:46 +0000359 self.wfile.write('<html><head><title>%s</title></head></html>' %
360 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000361
362 return True
363
364 def CacheMustRevalidateMaxAgeHandler(self):
365 """This request handler yields a page with the title set to the current
366 system time, and does not allow caching event though max-age of 60
367 seconds is specified."""
368
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000369 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000370 return False
371
372 self.send_response(200)
373 self.send_header('Content-type', 'text/html')
374 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
375 self.end_headers()
376
maruel@google.come250a9b2009-03-10 17:39:46 +0000377 self.wfile.write('<html><head><title>%s</title></head></html>' %
378 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000379
380 return True
381
initial.commit94958cf2008-07-26 22:42:52 +0000382 def CacheNoStoreHandler(self):
383 """This request handler yields a page with the title set to the current
384 system time, and does not allow the page to be stored."""
385
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000386 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000387 return False
388
389 self.send_response(200)
390 self.send_header('Content-type', 'text/html')
391 self.send_header('Cache-Control', 'no-store')
392 self.end_headers()
393
maruel@google.come250a9b2009-03-10 17:39:46 +0000394 self.wfile.write('<html><head><title>%s</title></head></html>' %
395 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000396
397 return True
398
399 def CacheNoStoreMaxAgeHandler(self):
400 """This request handler yields a page with the title set to the current
401 system time, and does not allow the page to be stored even though max-age
402 of 60 seconds is specified."""
403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
408 self.send_header('Content-type', 'text/html')
409 self.send_header('Cache-Control', 'max-age=60, no-store')
410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417
418 def CacheNoTransformHandler(self):
419 """This request handler yields a page with the title set to the current
420 system time, and does not allow the content to transformed during
421 user-agent caching"""
422
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000423 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000424 return False
425
426 self.send_response(200)
427 self.send_header('Content-type', 'text/html')
428 self.send_header('Cache-Control', 'no-transform')
429 self.end_headers()
430
maruel@google.come250a9b2009-03-10 17:39:46 +0000431 self.wfile.write('<html><head><title>%s</title></head></html>' %
432 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000433
434 return True
435
436 def EchoHeader(self):
437 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000438 """The only difference between this function and the EchoHeaderOverride"""
439 """function is in the parameter being passed to the helper function"""
440 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000441
ananta@chromium.org219b2062009-10-23 16:09:41 +0000442 def EchoHeaderOverride(self):
443 """This handler echoes back the value of a specific request header."""
444 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
445 """IE to issue HTTP requests using the host network stack."""
446 """The Accept and Charset tests which expect the server to echo back"""
447 """the corresponding headers fail here as IE returns cached responses"""
448 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
449 """treats this request as a new request and does not cache it."""
450 return self.EchoHeaderHelper("/echoheaderoverride")
451
452 def EchoHeaderHelper(self, echo_header):
453 """This function echoes back the value of the request header passed in."""
454 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000455 return False
456
457 query_char = self.path.find('?')
458 if query_char != -1:
459 header_name = self.path[query_char+1:]
460
461 self.send_response(200)
462 self.send_header('Content-type', 'text/plain')
463 self.send_header('Cache-control', 'max-age=60000')
464 # insert a vary header to properly indicate that the cachability of this
465 # request is subject to value of the request header being echoed.
466 if len(header_name) > 0:
467 self.send_header('Vary', header_name)
468 self.end_headers()
469
470 if len(header_name) > 0:
471 self.wfile.write(self.headers.getheader(header_name))
472
473 return True
474
475 def EchoHandler(self):
476 """This handler just echoes back the payload of the request, for testing
477 form submission."""
478
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000480 return False
481
482 self.send_response(200)
483 self.send_header('Content-type', 'text/html')
484 self.end_headers()
485 length = int(self.headers.getheader('content-length'))
486 request = self.rfile.read(length)
487 self.wfile.write(request)
488 return True
489
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000490 def WriteFile(self):
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000491 """This is handler dumps the content of POST/PUT request to a disk file
492 into the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000493
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000494 prefix='/writefile/'
495 if not self.path.startswith(prefix):
496 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000497
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000498 file_name = self.path[len(prefix):]
499
500 # do not allow fancy chars in file name
501 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
502 if len(file_name) and file_name[0] != '.':
503 path = os.path.join(self.server.data_dir, 'dump', file_name);
504 length = int(self.headers.getheader('content-length'))
505 request = self.rfile.read(length)
506 f = open(path, "wb")
507 f.write(request);
508 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000509
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000510 self.send_response(200)
511 self.send_header('Content-type', 'text/html')
512 self.end_headers()
513 self.wfile.write('<html>%s</html>' % file_name)
514 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000515
initial.commit94958cf2008-07-26 22:42:52 +0000516 def EchoTitleHandler(self):
517 """This handler is like Echo, but sets the page title to the request."""
518
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000519 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000520 return False
521
522 self.send_response(200)
523 self.send_header('Content-type', 'text/html')
524 self.end_headers()
525 length = int(self.headers.getheader('content-length'))
526 request = self.rfile.read(length)
527 self.wfile.write('<html><head><title>')
528 self.wfile.write(request)
529 self.wfile.write('</title></head></html>')
530 return True
531
532 def EchoAllHandler(self):
533 """This handler yields a (more) human-readable page listing information
534 about the request header & contents."""
535
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000536 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000537 return False
538
539 self.send_response(200)
540 self.send_header('Content-type', 'text/html')
541 self.end_headers()
542 self.wfile.write('<html><head><style>'
543 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
544 '</style></head><body>'
545 '<div style="float: right">'
546 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
547 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000548
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000549 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000550 length = int(self.headers.getheader('content-length'))
551 qs = self.rfile.read(length)
552 params = cgi.parse_qs(qs, keep_blank_values=1)
553
554 for param in params:
555 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000556
557 self.wfile.write('</pre>')
558
559 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
560
561 self.wfile.write('</body></html>')
562 return True
563
564 def DownloadHandler(self):
565 """This handler sends a downloadable file with or without reporting
566 the size (6K)."""
567
568 if self.path.startswith("/download-unknown-size"):
569 send_length = False
570 elif self.path.startswith("/download-known-size"):
571 send_length = True
572 else:
573 return False
574
575 #
576 # The test which uses this functionality is attempting to send
577 # small chunks of data to the client. Use a fairly large buffer
578 # so that we'll fill chrome's IO buffer enough to force it to
579 # actually write the data.
580 # See also the comments in the client-side of this test in
581 # download_uitest.cc
582 #
583 size_chunk1 = 35*1024
584 size_chunk2 = 10*1024
585
586 self.send_response(200)
587 self.send_header('Content-type', 'application/octet-stream')
588 self.send_header('Cache-Control', 'max-age=0')
589 if send_length:
590 self.send_header('Content-Length', size_chunk1 + size_chunk2)
591 self.end_headers()
592
593 # First chunk of data:
594 self.wfile.write("*" * size_chunk1)
595 self.wfile.flush()
596
597 # handle requests until one of them clears this flag.
598 self.server.waitForDownload = True
599 while self.server.waitForDownload:
600 self.server.handle_request()
601
602 # Second chunk of data:
603 self.wfile.write("*" * size_chunk2)
604 return True
605
606 def DownloadFinishHandler(self):
607 """This handler just tells the server to finish the current download."""
608
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000609 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000610 return False
611
612 self.server.waitForDownload = False
613 self.send_response(200)
614 self.send_header('Content-type', 'text/html')
615 self.send_header('Cache-Control', 'max-age=0')
616 self.end_headers()
617 return True
618
619 def FileHandler(self):
620 """This handler sends the contents of the requested file. Wow, it's like
621 a real webserver!"""
622
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000623 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000624 if not self.path.startswith(prefix):
625 return False
626
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000627 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000628 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000629 self.rfile.read(int(self.headers.getheader('content-length')))
630
initial.commit94958cf2008-07-26 22:42:52 +0000631 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000632 if file.find('?') > -1:
633 # Ignore the query parameters entirely.
634 url, querystring = file.split('?')
635 else:
636 url = file
637 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000638 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000639 if os.path.isdir(path):
640 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000641
642 if not os.path.isfile(path):
643 print "File not found " + file + " full path:" + path
644 self.send_error(404)
645 return True
646
647 f = open(path, "rb")
648 data = f.read()
649 f.close()
650
651 # If file.mock-http-headers exists, it contains the headers we
652 # should send. Read them in and parse them.
653 headers_path = path + '.mock-http-headers'
654 if os.path.isfile(headers_path):
655 f = open(headers_path, "r")
656
657 # "HTTP/1.1 200 OK"
658 response = f.readline()
659 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
660 self.send_response(int(status_code))
661
662 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000663 header_values = re.findall('(\S+):\s*(.*)', line)
664 if len(header_values) > 0:
665 # "name: value"
666 name, value = header_values[0]
667 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000668 f.close()
669 else:
670 # Could be more generic once we support mime-type sniffing, but for
671 # now we need to set it explicitly.
672 self.send_response(200)
673 self.send_header('Content-type', self.GetMIMETypeFromName(file))
674 self.send_header('Content-Length', len(data))
675 self.end_headers()
676
677 self.wfile.write(data)
678
679 return True
680
681 def RealFileWithCommonHeaderHandler(self):
682 """This handler sends the contents of the requested file without the pseudo
683 http head!"""
684
685 prefix='/realfiles/'
686 if not self.path.startswith(prefix):
687 return False
688
689 file = self.path[len(prefix):]
690 path = os.path.join(self.server.data_dir, file)
691
692 try:
693 f = open(path, "rb")
694 data = f.read()
695 f.close()
696
697 # just simply set the MIME as octal stream
698 self.send_response(200)
699 self.send_header('Content-type', 'application/octet-stream')
700 self.end_headers()
701
702 self.wfile.write(data)
703 except:
704 self.send_error(404)
705
706 return True
707
708 def RealBZ2FileWithCommonHeaderHandler(self):
709 """This handler sends the bzip2 contents of the requested file with
710 corresponding Content-Encoding field in http head!"""
711
712 prefix='/realbz2files/'
713 if not self.path.startswith(prefix):
714 return False
715
716 parts = self.path.split('?')
717 file = parts[0][len(prefix):]
718 path = os.path.join(self.server.data_dir, file) + '.bz2'
719
720 if len(parts) > 1:
721 options = parts[1]
722 else:
723 options = ''
724
725 try:
726 self.send_response(200)
727 accept_encoding = self.headers.get("Accept-Encoding")
728 if accept_encoding.find("bzip2") != -1:
729 f = open(path, "rb")
730 data = f.read()
731 f.close()
732 self.send_header('Content-Encoding', 'bzip2')
733 self.send_header('Content-type', 'application/x-bzip2')
734 self.end_headers()
735 if options == 'incremental-header':
736 self.wfile.write(data[:1])
737 self.wfile.flush()
738 time.sleep(1.0)
739 self.wfile.write(data[1:])
740 else:
741 self.wfile.write(data)
742 else:
743 """client do not support bzip2 format, send pseudo content
744 """
745 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
746 self.end_headers()
747 self.wfile.write("you do not support bzip2 encoding")
748 except:
749 self.send_error(404)
750
751 return True
752
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000753 def SetCookieHandler(self):
754 """This handler just sets a cookie, for testing cookie handling."""
755
756 if not self._ShouldHandleRequest("/set-cookie"):
757 return False
758
759 query_char = self.path.find('?')
760 if query_char != -1:
761 cookie_values = self.path[query_char + 1:].split('&')
762 else:
763 cookie_values = ("",)
764 self.send_response(200)
765 self.send_header('Content-type', 'text/html')
766 for cookie_value in cookie_values:
767 self.send_header('Set-Cookie', '%s' % cookie_value)
768 self.end_headers()
769 for cookie_value in cookie_values:
770 self.wfile.write('%s' % cookie_value)
771 return True
772
initial.commit94958cf2008-07-26 22:42:52 +0000773 def AuthBasicHandler(self):
774 """This handler tests 'Basic' authentication. It just sends a page with
775 title 'user/pass' if you succeed."""
776
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000777 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000778 return False
779
780 username = userpass = password = b64str = ""
781
ericroman@google.com239b4d82009-03-27 04:00:22 +0000782 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
783
initial.commit94958cf2008-07-26 22:42:52 +0000784 auth = self.headers.getheader('authorization')
785 try:
786 if not auth:
787 raise Exception('no auth')
788 b64str = re.findall(r'Basic (\S+)', auth)[0]
789 userpass = base64.b64decode(b64str)
790 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
791 if password != 'secret':
792 raise Exception('wrong password')
793 except Exception, e:
794 # Authentication failed.
795 self.send_response(401)
796 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
797 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000798 if set_cookie_if_challenged:
799 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000800 self.end_headers()
801 self.wfile.write('<html><head>')
802 self.wfile.write('<title>Denied: %s</title>' % e)
803 self.wfile.write('</head><body>')
804 self.wfile.write('auth=%s<p>' % auth)
805 self.wfile.write('b64str=%s<p>' % b64str)
806 self.wfile.write('username: %s<p>' % username)
807 self.wfile.write('userpass: %s<p>' % userpass)
808 self.wfile.write('password: %s<p>' % password)
809 self.wfile.write('You sent:<br>%s<p>' % self.headers)
810 self.wfile.write('</body></html>')
811 return True
812
813 # Authentication successful. (Return a cachable response to allow for
814 # testing cached pages that require authentication.)
815 if_none_match = self.headers.getheader('if-none-match')
816 if if_none_match == "abc":
817 self.send_response(304)
818 self.end_headers()
819 else:
820 self.send_response(200)
821 self.send_header('Content-type', 'text/html')
822 self.send_header('Cache-control', 'max-age=60000')
823 self.send_header('Etag', 'abc')
824 self.end_headers()
825 self.wfile.write('<html><head>')
826 self.wfile.write('<title>%s/%s</title>' % (username, password))
827 self.wfile.write('</head><body>')
828 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000829 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000830 self.wfile.write('</body></html>')
831
832 return True
833
tonyg@chromium.org75054202010-03-31 22:06:10 +0000834 def GetNonce(self, force_reset=False):
835 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000836
tonyg@chromium.org75054202010-03-31 22:06:10 +0000837 This is a fake implementation. A real implementation would only use a given
838 nonce a single time (hence the name n-once). However, for the purposes of
839 unittesting, we don't care about the security of the nonce.
840
841 Args:
842 force_reset: Iff set, the nonce will be changed. Useful for testing the
843 "stale" response.
844 """
845 if force_reset or not self.server.nonce_time:
846 self.server.nonce_time = time.time()
847 return _new_md5('privatekey%s%d' %
848 (self.path, self.server.nonce_time)).hexdigest()
849
850 def AuthDigestHandler(self):
851 """This handler tests 'Digest' authentication.
852
853 It just sends a page with title 'user/pass' if you succeed.
854
855 A stale response is sent iff "stale" is present in the request path.
856 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000857 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000858 return False
859
tonyg@chromium.org75054202010-03-31 22:06:10 +0000860 stale = 'stale' in self.path
861 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000862 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000863 password = 'secret'
864 realm = 'testrealm'
865
866 auth = self.headers.getheader('authorization')
867 pairs = {}
868 try:
869 if not auth:
870 raise Exception('no auth')
871 if not auth.startswith('Digest'):
872 raise Exception('not digest')
873 # Pull out all the name="value" pairs as a dictionary.
874 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
875
876 # Make sure it's all valid.
877 if pairs['nonce'] != nonce:
878 raise Exception('wrong nonce')
879 if pairs['opaque'] != opaque:
880 raise Exception('wrong opaque')
881
882 # Check the 'response' value and make sure it matches our magic hash.
883 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000884 hash_a1 = _new_md5(
885 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000886 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000887 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000888 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000889 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
890 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000891 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000892
893 if pairs['response'] != response:
894 raise Exception('wrong password')
895 except Exception, e:
896 # Authentication failed.
897 self.send_response(401)
898 hdr = ('Digest '
899 'realm="%s", '
900 'domain="/", '
901 'qop="auth", '
902 'algorithm=MD5, '
903 'nonce="%s", '
904 'opaque="%s"') % (realm, nonce, opaque)
905 if stale:
906 hdr += ', stale="TRUE"'
907 self.send_header('WWW-Authenticate', hdr)
908 self.send_header('Content-type', 'text/html')
909 self.end_headers()
910 self.wfile.write('<html><head>')
911 self.wfile.write('<title>Denied: %s</title>' % e)
912 self.wfile.write('</head><body>')
913 self.wfile.write('auth=%s<p>' % auth)
914 self.wfile.write('pairs=%s<p>' % pairs)
915 self.wfile.write('You sent:<br>%s<p>' % self.headers)
916 self.wfile.write('We are replying:<br>%s<p>' % hdr)
917 self.wfile.write('</body></html>')
918 return True
919
920 # Authentication successful.
921 self.send_response(200)
922 self.send_header('Content-type', 'text/html')
923 self.end_headers()
924 self.wfile.write('<html><head>')
925 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
926 self.wfile.write('</head><body>')
927 self.wfile.write('auth=%s<p>' % auth)
928 self.wfile.write('pairs=%s<p>' % pairs)
929 self.wfile.write('</body></html>')
930
931 return True
932
933 def SlowServerHandler(self):
934 """Wait for the user suggested time before responding. The syntax is
935 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000936 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000937 return False
938 query_char = self.path.find('?')
939 wait_sec = 1.0
940 if query_char >= 0:
941 try:
942 wait_sec = int(self.path[query_char + 1:])
943 except ValueError:
944 pass
945 time.sleep(wait_sec)
946 self.send_response(200)
947 self.send_header('Content-type', 'text/plain')
948 self.end_headers()
949 self.wfile.write("waited %d seconds" % wait_sec)
950 return True
951
952 def ContentTypeHandler(self):
953 """Returns a string of html with the given content type. E.g.,
954 /contenttype?text/css returns an html file with the Content-Type
955 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000956 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000957 return False
958 query_char = self.path.find('?')
959 content_type = self.path[query_char + 1:].strip()
960 if not content_type:
961 content_type = 'text/html'
962 self.send_response(200)
963 self.send_header('Content-Type', content_type)
964 self.end_headers()
965 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
966 return True
967
968 def ServerRedirectHandler(self):
969 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000970 '/server-redirect?http://foo.bar/asdf' to redirect to
971 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000972
973 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000974 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000975 return False
976
977 query_char = self.path.find('?')
978 if query_char < 0 or len(self.path) <= query_char + 1:
979 self.sendRedirectHelp(test_name)
980 return True
981 dest = self.path[query_char + 1:]
982
983 self.send_response(301) # moved permanently
984 self.send_header('Location', dest)
985 self.send_header('Content-type', 'text/html')
986 self.end_headers()
987 self.wfile.write('<html><head>')
988 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
989
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000990 return True
initial.commit94958cf2008-07-26 22:42:52 +0000991
992 def ClientRedirectHandler(self):
993 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000994 '/client-redirect?http://foo.bar/asdf' to redirect to
995 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000996
997 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000998 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000999 return False
1000
1001 query_char = self.path.find('?');
1002 if query_char < 0 or len(self.path) <= query_char + 1:
1003 self.sendRedirectHelp(test_name)
1004 return True
1005 dest = self.path[query_char + 1:]
1006
1007 self.send_response(200)
1008 self.send_header('Content-type', 'text/html')
1009 self.end_headers()
1010 self.wfile.write('<html><head>')
1011 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1012 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1013
1014 return True
1015
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001016 def ChromiumSyncTimeHandler(self):
1017 """Handle Chromium sync .../time requests.
1018
1019 The syncer sometimes checks server reachability by examining /time.
1020 """
1021 test_name = "/chromiumsync/time"
1022 if not self._ShouldHandleRequest(test_name):
1023 return False
1024
1025 self.send_response(200)
1026 self.send_header('Content-type', 'text/html')
1027 self.end_headers()
1028 return True
1029
skrul@chromium.orgfff501f2010-08-13 21:01:52 +00001030 def ChromiumSyncConfigureHandler(self):
1031 """Handle updating the configuration of the sync server.
1032
1033 The name and value pairs of the post payload will update the
1034 configuration of the sync server. Supported tuples are:
1035 user_email,<email address> - Sets the email for the fake user account
1036 """
1037 test_name = "/chromiumsync/configure"
1038 if not self._ShouldHandleRequest(test_name):
1039 return False
1040
1041 length = int(self.headers.getheader('content-length'))
1042 raw_request = self.rfile.read(length)
1043 config = cgi.parse_qs(raw_request, keep_blank_values=1)
1044
1045 success = self._sync_handler.HandleConfigure(config)
1046 if success:
1047 self.send_response(200)
1048 else:
1049 self.send_response(500)
1050 self.end_headers()
1051 return True
1052
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001053 def ChromiumSyncCommandHandler(self):
1054 """Handle a chromiumsync command arriving via http.
1055
1056 This covers all sync protocol commands: authentication, getupdates, and
1057 commit.
1058 """
1059 test_name = "/chromiumsync/command"
1060 if not self._ShouldHandleRequest(test_name):
1061 return False
1062
1063 length = int(self.headers.getheader('content-length'))
1064 raw_request = self.rfile.read(length)
1065
pathorn@chromium.org44920122010-07-27 18:25:35 +00001066 if not self.server._sync_handler:
1067 import chromiumsync
1068 self.server._sync_handler = chromiumsync.TestServer()
1069 http_response, raw_reply = self.server._sync_handler.HandleCommand(
1070 raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001071 self.send_response(http_response)
1072 self.end_headers()
1073 self.wfile.write(raw_reply)
1074 return True
1075
tony@chromium.org03266982010-03-05 03:18:42 +00001076 def MultipartHandler(self):
1077 """Send a multipart response (10 text/html pages)."""
1078 test_name = "/multipart"
1079 if not self._ShouldHandleRequest(test_name):
1080 return False
1081
1082 num_frames = 10
1083 bound = '12345'
1084 self.send_response(200)
1085 self.send_header('Content-type',
1086 'multipart/x-mixed-replace;boundary=' + bound)
1087 self.end_headers()
1088
1089 for i in xrange(num_frames):
1090 self.wfile.write('--' + bound + '\r\n')
1091 self.wfile.write('Content-type: text/html\r\n\r\n')
1092 self.wfile.write('<title>page ' + str(i) + '</title>')
1093 self.wfile.write('page ' + str(i))
1094
1095 self.wfile.write('--' + bound + '--')
1096 return True
1097
initial.commit94958cf2008-07-26 22:42:52 +00001098 def DefaultResponseHandler(self):
1099 """This is the catch-all response handler for requests that aren't handled
1100 by one of the special handlers above.
1101 Note that we specify the content-length as without it the https connection
1102 is not closed properly (and the browser keeps expecting data)."""
1103
1104 contents = "Default response given for path: " + self.path
1105 self.send_response(200)
1106 self.send_header('Content-type', 'text/html')
1107 self.send_header("Content-Length", len(contents))
1108 self.end_headers()
1109 self.wfile.write(contents)
1110 return True
1111
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001112 def RedirectConnectHandler(self):
1113 """Sends a redirect to the CONNECT request for www.redirect.com. This
1114 response is not specified by the RFC, so the browser should not follow
1115 the redirect."""
1116
1117 if (self.path.find("www.redirect.com") < 0):
1118 return False
1119
1120 dest = "http://www.destination.com/foo.js"
1121
1122 self.send_response(302) # moved temporarily
1123 self.send_header('Location', dest)
1124 self.send_header('Connection', 'close')
1125 self.end_headers()
1126 return True
1127
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001128 def ServerAuthConnectHandler(self):
1129 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1130 response doesn't make sense because the proxy server cannot request
1131 server authentication."""
1132
1133 if (self.path.find("www.server-auth.com") < 0):
1134 return False
1135
1136 challenge = 'Basic realm="WallyWorld"'
1137
1138 self.send_response(401) # unauthorized
1139 self.send_header('WWW-Authenticate', challenge)
1140 self.send_header('Connection', 'close')
1141 self.end_headers()
1142 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001143
1144 def DefaultConnectResponseHandler(self):
1145 """This is the catch-all response handler for CONNECT requests that aren't
1146 handled by one of the special handlers above. Real Web servers respond
1147 with 400 to CONNECT requests."""
1148
1149 contents = "Your client has issued a malformed or illegal request."
1150 self.send_response(400) # bad request
1151 self.send_header('Content-type', 'text/html')
1152 self.send_header("Content-Length", len(contents))
1153 self.end_headers()
1154 self.wfile.write(contents)
1155 return True
1156
1157 def do_CONNECT(self):
1158 for handler in self._connect_handlers:
1159 if handler():
1160 return
1161
initial.commit94958cf2008-07-26 22:42:52 +00001162 def do_GET(self):
1163 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001164 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001165 return
1166
1167 def do_POST(self):
1168 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001169 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001170 return
1171
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001172 def do_PUT(self):
1173 for handler in self._put_handlers:
1174 if handler():
1175 return
1176
initial.commit94958cf2008-07-26 22:42:52 +00001177 # called by the redirect handling function when there is no parameter
1178 def sendRedirectHelp(self, redirect_name):
1179 self.send_response(200)
1180 self.send_header('Content-type', 'text/html')
1181 self.end_headers()
1182 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1183 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1184 self.wfile.write('</body></html>')
1185
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001186def MakeDumpDir(data_dir):
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001187 """Create directory named 'dump' where uploaded data via HTTP POST/PUT
1188 requests will be stored. If the directory already exists all files and
1189 subdirectories will be deleted."""
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001190 dump_dir = os.path.join(data_dir, 'dump');
1191 if os.path.isdir(dump_dir):
1192 shutil.rmtree(dump_dir)
1193 os.mkdir(dump_dir)
1194
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001195def MakeDataDir():
1196 if options.data_dir:
1197 if not os.path.isdir(options.data_dir):
1198 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1199 return None
1200 my_data_dir = options.data_dir
1201 else:
1202 # Create the default path to our data dir, relative to the exe dir.
1203 my_data_dir = os.path.dirname(sys.argv[0])
1204 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001205 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001206
1207 #TODO(ibrar): Must use Find* funtion defined in google\tools
1208 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1209
1210 return my_data_dir
1211
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001212class FileMultiplexer:
1213 def __init__(self, fd1, fd2) :
1214 self.__fd1 = fd1
1215 self.__fd2 = fd2
1216
1217 def __del__(self) :
1218 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1219 self.__fd1.close()
1220 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1221 self.__fd2.close()
1222
1223 def write(self, text) :
1224 self.__fd1.write(text)
1225 self.__fd2.write(text)
1226
1227 def flush(self) :
1228 self.__fd1.flush()
1229 self.__fd2.flush()
1230
initial.commit94958cf2008-07-26 22:42:52 +00001231def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001232 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001233 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1234 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001235
1236 port = options.port
1237
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001238 if options.server_type == SERVER_HTTP:
1239 if options.cert:
1240 # let's make sure the cert file exists.
1241 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001242 print 'specified server cert file not found: ' + options.cert + \
1243 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001244 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001245 for ca_cert in options.ssl_client_ca:
1246 if not os.path.isfile(ca_cert):
1247 print 'specified trusted client CA file not found: ' + ca_cert + \
1248 ' exiting...'
1249 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001250 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001251 options.ssl_client_auth, options.ssl_client_ca)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001252 print 'HTTPS server started on port %d...' % port
1253 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001254 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001255 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001256
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001257 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001258 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001259 server._sync_handler = None
1260
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001261 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001262
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001263 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001264 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001265 my_data_dir = MakeDataDir()
1266
1267 def line_logger(msg):
1268 if (msg.find("kill") >= 0):
1269 server.stop = True
1270 print 'shutting down server'
1271 sys.exit(0)
1272
1273 # Instantiate a dummy authorizer for managing 'virtual' users
1274 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1275
1276 # Define a new user having full r/w permissions and a read-only
1277 # anonymous user
1278 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1279
1280 authorizer.add_anonymous(my_data_dir)
1281
1282 # Instantiate FTP handler class
1283 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1284 ftp_handler.authorizer = authorizer
1285 pyftpdlib.ftpserver.logline = line_logger
1286
1287 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001288 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1289 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001290
1291 # Instantiate FTP server class and listen to 127.0.0.1:port
1292 address = ('127.0.0.1', port)
1293 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1294 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001295
1296 try:
1297 server.serve_forever()
1298 except KeyboardInterrupt:
1299 print 'shutting down server'
1300 server.stop = True
1301
1302if __name__ == '__main__':
1303 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001304 option_parser.add_option("-f", '--ftp', action='store_const',
1305 const=SERVER_FTP, default=SERVER_HTTP,
1306 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001307 help='FTP or HTTP server: default is HTTP.')
initial.commit94958cf2008-07-26 22:42:52 +00001308 option_parser.add_option('', '--port', default='8888', type='int',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001309 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001310 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001311 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001312 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001313 help='Specify that https should be used, specify '
1314 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001315 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001316 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1317 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001318 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1319 help='Specify that the client certificate request '
1320 'should indicate that it supports the CA contained '
1321 'in the specified certificate file')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001322 option_parser.add_option('', '--file-root-url', default='/files/',
1323 help='Specify a root URL for files served.')
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001324 option_parser.add_option('', '--never-die', default=False,
1325 action="store_true",
1326 help='Prevent the server from dying when visiting '
1327 'a /kill URL. Useful for manually running some '
1328 'tests.')
initial.commit94958cf2008-07-26 22:42:52 +00001329 options, args = option_parser.parse_args()
1330
1331 sys.exit(main(options, args))