blob: c3fe86b4b9b130f24a42b420b357484125b14a2a [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.
nick@chromium.org8daefd62010-10-27 23:24:05 +00009It defaults to living on localhost:8888.
initial.commit94958cf2008-07-26 22:42:52 +000010It can use https if you specify the flag --https=CERT where CERT is the path
11to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000012"""
13
14import base64
15import BaseHTTPServer
16import cgi
initial.commit94958cf2008-07-26 22:42:52 +000017import optparse
18import os
19import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000020import shutil
initial.commit94958cf2008-07-26 22:42:52 +000021import SocketServer
22import sys
23import time
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000024import urllib2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000025import warnings
26
27# Ignore deprecation warnings, they make our output more cluttered.
28warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000029
30import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000031import tlslite
32import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000033
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000034try:
35 import hashlib
36 _new_md5 = hashlib.md5
37except ImportError:
38 import md5
39 _new_md5 = md5.new
40
davidben@chromium.org06fcf202010-09-22 18:15:23 +000041if sys.platform == 'win32':
42 import msvcrt
43
maruel@chromium.org756cf982009-03-05 12:46:38 +000044SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000045SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000046
47debug_output = sys.stderr
48def debug(str):
49 debug_output.write(str + "\n")
50 debug_output.flush()
51
52class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
53 """This is a specialization of of BaseHTTPServer to allow it
54 to be exited cleanly (by setting its "stop" member to True)."""
55
56 def serve_forever(self):
57 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000058 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000059 while not self.stop:
60 self.handle_request()
61 self.socket.close()
62
63class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
64 """This is a specialization of StoppableHTTPerver that add https support."""
65
davidben@chromium.org31282a12010-08-07 01:10:02 +000066 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000067 ssl_client_auth, ssl_client_cas):
initial.commit94958cf2008-07-26 22:42:52 +000068 s = open(cert_path).read()
69 x509 = tlslite.api.X509()
70 x509.parse(s)
71 self.cert_chain = tlslite.api.X509CertChain([x509])
72 s = open(cert_path).read()
73 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000074 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000075 self.ssl_client_cas = []
76 for ca_file in ssl_client_cas:
77 s = open(ca_file).read()
78 x509 = tlslite.api.X509()
79 x509.parse(s)
80 self.ssl_client_cas.append(x509.subject)
initial.commit94958cf2008-07-26 22:42:52 +000081
82 self.session_cache = tlslite.api.SessionCache()
83 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
84
85 def handshake(self, tlsConnection):
86 """Creates the SSL connection."""
87 try:
88 tlsConnection.handshakeServer(certChain=self.cert_chain,
89 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +000090 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000091 reqCert=self.ssl_client_auth,
92 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +000093 tlsConnection.ignoreAbruptClose = True
94 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000095 except tlslite.api.TLSAbruptCloseError:
96 # Ignore abrupt close.
97 return True
initial.commit94958cf2008-07-26 22:42:52 +000098 except tlslite.api.TLSError, error:
99 print "Handshake failure:", str(error)
100 return False
101
102class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
103
104 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000105 self._connect_handlers = [
106 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000107 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000108 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000109 self._get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000110 self.NoCacheMaxAgeTimeHandler,
111 self.NoCacheTimeHandler,
112 self.CacheTimeHandler,
113 self.CacheExpiresHandler,
114 self.CacheProxyRevalidateHandler,
115 self.CachePrivateHandler,
116 self.CachePublicHandler,
117 self.CacheSMaxAgeHandler,
118 self.CacheMustRevalidateHandler,
119 self.CacheMustRevalidateMaxAgeHandler,
120 self.CacheNoStoreHandler,
121 self.CacheNoStoreMaxAgeHandler,
122 self.CacheNoTransformHandler,
123 self.DownloadHandler,
124 self.DownloadFinishHandler,
125 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000126 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000127 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000128 self.FileHandler,
129 self.RealFileWithCommonHeaderHandler,
130 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000131 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000132 self.AuthBasicHandler,
133 self.AuthDigestHandler,
134 self.SlowServerHandler,
135 self.ContentTypeHandler,
136 self.ServerRedirectHandler,
137 self.ClientRedirectHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000138 self.ChromiumSyncTimeHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000139 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000140 self.DefaultResponseHandler]
141 self._post_handlers = [
142 self.EchoTitleHandler,
143 self.EchoAllHandler,
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000144 self.ChromiumSyncCommandHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000145 self.EchoHandler] + self._get_handlers
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000146 self._put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000147 self.EchoTitleHandler,
148 self.EchoAllHandler,
149 self.EchoHandler] + self._get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000150
maruel@google.come250a9b2009-03-10 17:39:46 +0000151 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000152 'crx' : 'application/x-chrome-extension',
maruel@google.come250a9b2009-03-10 17:39:46 +0000153 'gif': 'image/gif',
154 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000155 'jpg' : 'image/jpeg',
156 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000157 }
initial.commit94958cf2008-07-26 22:42:52 +0000158 self._default_mime_type = 'text/html'
159
maruel@google.come250a9b2009-03-10 17:39:46 +0000160 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
161 client_address,
162 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000163
phajdan.jr@chromium.orgbe840252010-08-26 16:25:19 +0000164 def log_request(self, *args, **kwargs):
165 # Disable request logging to declutter test log output.
166 pass
167
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000168 def _ShouldHandleRequest(self, handler_name):
169 """Determines if the path can be handled by the handler.
170
171 We consider a handler valid if the path begins with the
172 handler name. It can optionally be followed by "?*", "/*".
173 """
174
175 pattern = re.compile('%s($|\?|/).*' % handler_name)
176 return pattern.match(self.path)
177
initial.commit94958cf2008-07-26 22:42:52 +0000178 def GetMIMETypeFromName(self, file_name):
179 """Returns the mime type for the specified file_name. So far it only looks
180 at the file extension."""
181
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000182 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000183 if len(extension) == 0:
184 # no extension.
185 return self._default_mime_type
186
ericroman@google.comc17ca532009-05-07 03:51:05 +0000187 # extension starts with a dot, so we need to remove it
188 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000189
initial.commit94958cf2008-07-26 22:42:52 +0000190 def NoCacheMaxAgeTimeHandler(self):
191 """This request handler yields a page with the title set to the current
192 system time, and no caching requested."""
193
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000194 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000195 return False
196
197 self.send_response(200)
198 self.send_header('Cache-Control', 'max-age=0')
199 self.send_header('Content-type', 'text/html')
200 self.end_headers()
201
maruel@google.come250a9b2009-03-10 17:39:46 +0000202 self.wfile.write('<html><head><title>%s</title></head></html>' %
203 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000204
205 return True
206
207 def NoCacheTimeHandler(self):
208 """This request handler yields a page with the title set to the current
209 system time, and no caching requested."""
210
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000211 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000212 return False
213
214 self.send_response(200)
215 self.send_header('Cache-Control', 'no-cache')
216 self.send_header('Content-type', 'text/html')
217 self.end_headers()
218
maruel@google.come250a9b2009-03-10 17:39:46 +0000219 self.wfile.write('<html><head><title>%s</title></head></html>' %
220 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000221
222 return True
223
224 def CacheTimeHandler(self):
225 """This request handler yields a page with the title set to the current
226 system time, and allows caching for one minute."""
227
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000228 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000229 return False
230
231 self.send_response(200)
232 self.send_header('Cache-Control', 'max-age=60')
233 self.send_header('Content-type', 'text/html')
234 self.end_headers()
235
maruel@google.come250a9b2009-03-10 17:39:46 +0000236 self.wfile.write('<html><head><title>%s</title></head></html>' %
237 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000238
239 return True
240
241 def CacheExpiresHandler(self):
242 """This request handler yields a page with the title set to the current
243 system time, and set the page to expire on 1 Jan 2099."""
244
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000245 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000246 return False
247
248 self.send_response(200)
249 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
250 self.send_header('Content-type', 'text/html')
251 self.end_headers()
252
maruel@google.come250a9b2009-03-10 17:39:46 +0000253 self.wfile.write('<html><head><title>%s</title></head></html>' %
254 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000255
256 return True
257
258 def CacheProxyRevalidateHandler(self):
259 """This request handler yields a page with the title set to the current
260 system time, and allows caching for 60 seconds"""
261
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000262 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000263 return False
264
265 self.send_response(200)
266 self.send_header('Content-type', 'text/html')
267 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
268 self.end_headers()
269
maruel@google.come250a9b2009-03-10 17:39:46 +0000270 self.wfile.write('<html><head><title>%s</title></head></html>' %
271 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000272
273 return True
274
275 def CachePrivateHandler(self):
276 """This request handler yields a page with the title set to the current
277 system time, and allows caching for 5 seconds."""
278
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000279 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000280 return False
281
282 self.send_response(200)
283 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000284 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000285 self.end_headers()
286
maruel@google.come250a9b2009-03-10 17:39:46 +0000287 self.wfile.write('<html><head><title>%s</title></head></html>' %
288 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000289
290 return True
291
292 def CachePublicHandler(self):
293 """This request handler yields a page with the title set to the current
294 system time, and allows caching for 5 seconds."""
295
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000296 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000297 return False
298
299 self.send_response(200)
300 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000301 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000302 self.end_headers()
303
maruel@google.come250a9b2009-03-10 17:39:46 +0000304 self.wfile.write('<html><head><title>%s</title></head></html>' %
305 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000306
307 return True
308
309 def CacheSMaxAgeHandler(self):
310 """This request handler yields a page with the title set to the current
311 system time, and does not allow for caching."""
312
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000313 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000314 return False
315
316 self.send_response(200)
317 self.send_header('Content-type', 'text/html')
318 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
319 self.end_headers()
320
maruel@google.come250a9b2009-03-10 17:39:46 +0000321 self.wfile.write('<html><head><title>%s</title></head></html>' %
322 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000323
324 return True
325
326 def CacheMustRevalidateHandler(self):
327 """This request handler yields a page with the title set to the current
328 system time, and does not allow caching."""
329
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000330 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000331 return False
332
333 self.send_response(200)
334 self.send_header('Content-type', 'text/html')
335 self.send_header('Cache-Control', 'must-revalidate')
336 self.end_headers()
337
maruel@google.come250a9b2009-03-10 17:39:46 +0000338 self.wfile.write('<html><head><title>%s</title></head></html>' %
339 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000340
341 return True
342
343 def CacheMustRevalidateMaxAgeHandler(self):
344 """This request handler yields a page with the title set to the current
345 system time, and does not allow caching event though max-age of 60
346 seconds is specified."""
347
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000348 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000349 return False
350
351 self.send_response(200)
352 self.send_header('Content-type', 'text/html')
353 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
354 self.end_headers()
355
maruel@google.come250a9b2009-03-10 17:39:46 +0000356 self.wfile.write('<html><head><title>%s</title></head></html>' %
357 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000358
359 return True
360
initial.commit94958cf2008-07-26 22:42:52 +0000361 def CacheNoStoreHandler(self):
362 """This request handler yields a page with the title set to the current
363 system time, and does not allow the page to be stored."""
364
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000365 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000366 return False
367
368 self.send_response(200)
369 self.send_header('Content-type', 'text/html')
370 self.send_header('Cache-Control', 'no-store')
371 self.end_headers()
372
maruel@google.come250a9b2009-03-10 17:39:46 +0000373 self.wfile.write('<html><head><title>%s</title></head></html>' %
374 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000375
376 return True
377
378 def CacheNoStoreMaxAgeHandler(self):
379 """This request handler yields a page with the title set to the current
380 system time, and does not allow the page to be stored even though max-age
381 of 60 seconds is specified."""
382
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000383 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000384 return False
385
386 self.send_response(200)
387 self.send_header('Content-type', 'text/html')
388 self.send_header('Cache-Control', 'max-age=60, no-store')
389 self.end_headers()
390
maruel@google.come250a9b2009-03-10 17:39:46 +0000391 self.wfile.write('<html><head><title>%s</title></head></html>' %
392 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000393
394 return True
395
396
397 def CacheNoTransformHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and does not allow the content to transformed during
400 user-agent caching"""
401
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000402 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000403 return False
404
405 self.send_response(200)
406 self.send_header('Content-type', 'text/html')
407 self.send_header('Cache-Control', 'no-transform')
408 self.end_headers()
409
maruel@google.come250a9b2009-03-10 17:39:46 +0000410 self.wfile.write('<html><head><title>%s</title></head></html>' %
411 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000412
413 return True
414
415 def EchoHeader(self):
416 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000417 """The only difference between this function and the EchoHeaderOverride"""
418 """function is in the parameter being passed to the helper function"""
419 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000420
ananta@chromium.org219b2062009-10-23 16:09:41 +0000421 def EchoHeaderOverride(self):
422 """This handler echoes back the value of a specific request header."""
423 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
424 """IE to issue HTTP requests using the host network stack."""
425 """The Accept and Charset tests which expect the server to echo back"""
426 """the corresponding headers fail here as IE returns cached responses"""
427 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
428 """treats this request as a new request and does not cache it."""
429 return self.EchoHeaderHelper("/echoheaderoverride")
430
431 def EchoHeaderHelper(self, echo_header):
432 """This function echoes back the value of the request header passed in."""
433 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000434 return False
435
436 query_char = self.path.find('?')
437 if query_char != -1:
438 header_name = self.path[query_char+1:]
439
440 self.send_response(200)
441 self.send_header('Content-type', 'text/plain')
442 self.send_header('Cache-control', 'max-age=60000')
443 # insert a vary header to properly indicate that the cachability of this
444 # request is subject to value of the request header being echoed.
445 if len(header_name) > 0:
446 self.send_header('Vary', header_name)
447 self.end_headers()
448
449 if len(header_name) > 0:
450 self.wfile.write(self.headers.getheader(header_name))
451
452 return True
453
454 def EchoHandler(self):
455 """This handler just echoes back the payload of the request, for testing
456 form submission."""
457
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000458 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000459 return False
460
461 self.send_response(200)
462 self.send_header('Content-type', 'text/html')
463 self.end_headers()
464 length = int(self.headers.getheader('content-length'))
465 request = self.rfile.read(length)
466 self.wfile.write(request)
467 return True
468
469 def EchoTitleHandler(self):
470 """This handler is like Echo, but sets the page title to the request."""
471
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000472 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000473 return False
474
475 self.send_response(200)
476 self.send_header('Content-type', 'text/html')
477 self.end_headers()
478 length = int(self.headers.getheader('content-length'))
479 request = self.rfile.read(length)
480 self.wfile.write('<html><head><title>')
481 self.wfile.write(request)
482 self.wfile.write('</title></head></html>')
483 return True
484
485 def EchoAllHandler(self):
486 """This handler yields a (more) human-readable page listing information
487 about the request header & contents."""
488
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000489 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000490 return False
491
492 self.send_response(200)
493 self.send_header('Content-type', 'text/html')
494 self.end_headers()
495 self.wfile.write('<html><head><style>'
496 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
497 '</style></head><body>'
498 '<div style="float: right">'
nick@chromium.org8daefd62010-10-27 23:24:05 +0000499 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000500 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000501
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000502 if self.command == 'POST' or self.command == 'PUT':
ericroman@google.coma47622b2008-11-15 04:36:51 +0000503 length = int(self.headers.getheader('content-length'))
504 qs = self.rfile.read(length)
505 params = cgi.parse_qs(qs, keep_blank_values=1)
506
507 for param in params:
508 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000509
510 self.wfile.write('</pre>')
511
512 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
513
514 self.wfile.write('</body></html>')
515 return True
516
517 def DownloadHandler(self):
518 """This handler sends a downloadable file with or without reporting
519 the size (6K)."""
520
521 if self.path.startswith("/download-unknown-size"):
522 send_length = False
523 elif self.path.startswith("/download-known-size"):
524 send_length = True
525 else:
526 return False
527
528 #
529 # The test which uses this functionality is attempting to send
530 # small chunks of data to the client. Use a fairly large buffer
531 # so that we'll fill chrome's IO buffer enough to force it to
532 # actually write the data.
533 # See also the comments in the client-side of this test in
534 # download_uitest.cc
535 #
536 size_chunk1 = 35*1024
537 size_chunk2 = 10*1024
538
539 self.send_response(200)
540 self.send_header('Content-type', 'application/octet-stream')
541 self.send_header('Cache-Control', 'max-age=0')
542 if send_length:
543 self.send_header('Content-Length', size_chunk1 + size_chunk2)
544 self.end_headers()
545
546 # First chunk of data:
547 self.wfile.write("*" * size_chunk1)
548 self.wfile.flush()
549
550 # handle requests until one of them clears this flag.
551 self.server.waitForDownload = True
552 while self.server.waitForDownload:
553 self.server.handle_request()
554
555 # Second chunk of data:
556 self.wfile.write("*" * size_chunk2)
557 return True
558
559 def DownloadFinishHandler(self):
560 """This handler just tells the server to finish the current download."""
561
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000562 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000563 return False
564
565 self.server.waitForDownload = False
566 self.send_response(200)
567 self.send_header('Content-type', 'text/html')
568 self.send_header('Cache-Control', 'max-age=0')
569 self.end_headers()
570 return True
571
572 def FileHandler(self):
573 """This handler sends the contents of the requested file. Wow, it's like
574 a real webserver!"""
575
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000576 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000577 if not self.path.startswith(prefix):
578 return False
579
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000580 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000581 if self.command == 'POST' or self.command == 'PUT' :
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000582 self.rfile.read(int(self.headers.getheader('content-length')))
583
initial.commit94958cf2008-07-26 22:42:52 +0000584 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000585 if file.find('?') > -1:
586 # Ignore the query parameters entirely.
587 url, querystring = file.split('?')
588 else:
589 url = file
590 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000591 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000592 if os.path.isdir(path):
593 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000594
595 if not os.path.isfile(path):
596 print "File not found " + file + " full path:" + path
597 self.send_error(404)
598 return True
599
600 f = open(path, "rb")
601 data = f.read()
602 f.close()
603
604 # If file.mock-http-headers exists, it contains the headers we
605 # should send. Read them in and parse them.
606 headers_path = path + '.mock-http-headers'
607 if os.path.isfile(headers_path):
608 f = open(headers_path, "r")
609
610 # "HTTP/1.1 200 OK"
611 response = f.readline()
612 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
613 self.send_response(int(status_code))
614
615 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000616 header_values = re.findall('(\S+):\s*(.*)', line)
617 if len(header_values) > 0:
618 # "name: value"
619 name, value = header_values[0]
620 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000621 f.close()
622 else:
623 # Could be more generic once we support mime-type sniffing, but for
624 # now we need to set it explicitly.
625 self.send_response(200)
626 self.send_header('Content-type', self.GetMIMETypeFromName(file))
627 self.send_header('Content-Length', len(data))
628 self.end_headers()
629
630 self.wfile.write(data)
631
632 return True
633
634 def RealFileWithCommonHeaderHandler(self):
635 """This handler sends the contents of the requested file without the pseudo
636 http head!"""
637
638 prefix='/realfiles/'
639 if not self.path.startswith(prefix):
640 return False
641
642 file = self.path[len(prefix):]
643 path = os.path.join(self.server.data_dir, file)
644
645 try:
646 f = open(path, "rb")
647 data = f.read()
648 f.close()
649
650 # just simply set the MIME as octal stream
651 self.send_response(200)
652 self.send_header('Content-type', 'application/octet-stream')
653 self.end_headers()
654
655 self.wfile.write(data)
656 except:
657 self.send_error(404)
658
659 return True
660
661 def RealBZ2FileWithCommonHeaderHandler(self):
662 """This handler sends the bzip2 contents of the requested file with
663 corresponding Content-Encoding field in http head!"""
664
665 prefix='/realbz2files/'
666 if not self.path.startswith(prefix):
667 return False
668
669 parts = self.path.split('?')
670 file = parts[0][len(prefix):]
671 path = os.path.join(self.server.data_dir, file) + '.bz2'
672
673 if len(parts) > 1:
674 options = parts[1]
675 else:
676 options = ''
677
678 try:
679 self.send_response(200)
680 accept_encoding = self.headers.get("Accept-Encoding")
681 if accept_encoding.find("bzip2") != -1:
682 f = open(path, "rb")
683 data = f.read()
684 f.close()
685 self.send_header('Content-Encoding', 'bzip2')
686 self.send_header('Content-type', 'application/x-bzip2')
687 self.end_headers()
688 if options == 'incremental-header':
689 self.wfile.write(data[:1])
690 self.wfile.flush()
691 time.sleep(1.0)
692 self.wfile.write(data[1:])
693 else:
694 self.wfile.write(data)
695 else:
696 """client do not support bzip2 format, send pseudo content
697 """
698 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
699 self.end_headers()
700 self.wfile.write("you do not support bzip2 encoding")
701 except:
702 self.send_error(404)
703
704 return True
705
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000706 def SetCookieHandler(self):
707 """This handler just sets a cookie, for testing cookie handling."""
708
709 if not self._ShouldHandleRequest("/set-cookie"):
710 return False
711
712 query_char = self.path.find('?')
713 if query_char != -1:
714 cookie_values = self.path[query_char + 1:].split('&')
715 else:
716 cookie_values = ("",)
717 self.send_response(200)
718 self.send_header('Content-type', 'text/html')
719 for cookie_value in cookie_values:
720 self.send_header('Set-Cookie', '%s' % cookie_value)
721 self.end_headers()
722 for cookie_value in cookie_values:
723 self.wfile.write('%s' % cookie_value)
724 return True
725
initial.commit94958cf2008-07-26 22:42:52 +0000726 def AuthBasicHandler(self):
727 """This handler tests 'Basic' authentication. It just sends a page with
728 title 'user/pass' if you succeed."""
729
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000730 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000731 return False
732
733 username = userpass = password = b64str = ""
734
ericroman@google.com239b4d82009-03-27 04:00:22 +0000735 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
736
initial.commit94958cf2008-07-26 22:42:52 +0000737 auth = self.headers.getheader('authorization')
738 try:
739 if not auth:
740 raise Exception('no auth')
741 b64str = re.findall(r'Basic (\S+)', auth)[0]
742 userpass = base64.b64decode(b64str)
743 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
744 if password != 'secret':
745 raise Exception('wrong password')
746 except Exception, e:
747 # Authentication failed.
748 self.send_response(401)
749 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
750 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000751 if set_cookie_if_challenged:
752 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000753 self.end_headers()
754 self.wfile.write('<html><head>')
755 self.wfile.write('<title>Denied: %s</title>' % e)
756 self.wfile.write('</head><body>')
757 self.wfile.write('auth=%s<p>' % auth)
758 self.wfile.write('b64str=%s<p>' % b64str)
759 self.wfile.write('username: %s<p>' % username)
760 self.wfile.write('userpass: %s<p>' % userpass)
761 self.wfile.write('password: %s<p>' % password)
762 self.wfile.write('You sent:<br>%s<p>' % self.headers)
763 self.wfile.write('</body></html>')
764 return True
765
766 # Authentication successful. (Return a cachable response to allow for
767 # testing cached pages that require authentication.)
768 if_none_match = self.headers.getheader('if-none-match')
769 if if_none_match == "abc":
770 self.send_response(304)
771 self.end_headers()
772 else:
773 self.send_response(200)
774 self.send_header('Content-type', 'text/html')
775 self.send_header('Cache-control', 'max-age=60000')
776 self.send_header('Etag', 'abc')
777 self.end_headers()
778 self.wfile.write('<html><head>')
779 self.wfile.write('<title>%s/%s</title>' % (username, password))
780 self.wfile.write('</head><body>')
781 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000782 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000783 self.wfile.write('</body></html>')
784
785 return True
786
tonyg@chromium.org75054202010-03-31 22:06:10 +0000787 def GetNonce(self, force_reset=False):
788 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +0000789
tonyg@chromium.org75054202010-03-31 22:06:10 +0000790 This is a fake implementation. A real implementation would only use a given
791 nonce a single time (hence the name n-once). However, for the purposes of
792 unittesting, we don't care about the security of the nonce.
793
794 Args:
795 force_reset: Iff set, the nonce will be changed. Useful for testing the
796 "stale" response.
797 """
798 if force_reset or not self.server.nonce_time:
799 self.server.nonce_time = time.time()
800 return _new_md5('privatekey%s%d' %
801 (self.path, self.server.nonce_time)).hexdigest()
802
803 def AuthDigestHandler(self):
804 """This handler tests 'Digest' authentication.
805
806 It just sends a page with title 'user/pass' if you succeed.
807
808 A stale response is sent iff "stale" is present in the request path.
809 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000810 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000811 return False
812
tonyg@chromium.org75054202010-03-31 22:06:10 +0000813 stale = 'stale' in self.path
814 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000815 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000816 password = 'secret'
817 realm = 'testrealm'
818
819 auth = self.headers.getheader('authorization')
820 pairs = {}
821 try:
822 if not auth:
823 raise Exception('no auth')
824 if not auth.startswith('Digest'):
825 raise Exception('not digest')
826 # Pull out all the name="value" pairs as a dictionary.
827 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
828
829 # Make sure it's all valid.
830 if pairs['nonce'] != nonce:
831 raise Exception('wrong nonce')
832 if pairs['opaque'] != opaque:
833 raise Exception('wrong opaque')
834
835 # Check the 'response' value and make sure it matches our magic hash.
836 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000837 hash_a1 = _new_md5(
838 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000839 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000840 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000841 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000842 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
843 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000844 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000845
846 if pairs['response'] != response:
847 raise Exception('wrong password')
848 except Exception, e:
849 # Authentication failed.
850 self.send_response(401)
851 hdr = ('Digest '
852 'realm="%s", '
853 'domain="/", '
854 'qop="auth", '
855 'algorithm=MD5, '
856 'nonce="%s", '
857 'opaque="%s"') % (realm, nonce, opaque)
858 if stale:
859 hdr += ', stale="TRUE"'
860 self.send_header('WWW-Authenticate', hdr)
861 self.send_header('Content-type', 'text/html')
862 self.end_headers()
863 self.wfile.write('<html><head>')
864 self.wfile.write('<title>Denied: %s</title>' % e)
865 self.wfile.write('</head><body>')
866 self.wfile.write('auth=%s<p>' % auth)
867 self.wfile.write('pairs=%s<p>' % pairs)
868 self.wfile.write('You sent:<br>%s<p>' % self.headers)
869 self.wfile.write('We are replying:<br>%s<p>' % hdr)
870 self.wfile.write('</body></html>')
871 return True
872
873 # Authentication successful.
874 self.send_response(200)
875 self.send_header('Content-type', 'text/html')
876 self.end_headers()
877 self.wfile.write('<html><head>')
878 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
879 self.wfile.write('</head><body>')
880 self.wfile.write('auth=%s<p>' % auth)
881 self.wfile.write('pairs=%s<p>' % pairs)
882 self.wfile.write('</body></html>')
883
884 return True
885
886 def SlowServerHandler(self):
887 """Wait for the user suggested time before responding. The syntax is
888 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000889 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000890 return False
891 query_char = self.path.find('?')
892 wait_sec = 1.0
893 if query_char >= 0:
894 try:
895 wait_sec = int(self.path[query_char + 1:])
896 except ValueError:
897 pass
898 time.sleep(wait_sec)
899 self.send_response(200)
900 self.send_header('Content-type', 'text/plain')
901 self.end_headers()
902 self.wfile.write("waited %d seconds" % wait_sec)
903 return True
904
905 def ContentTypeHandler(self):
906 """Returns a string of html with the given content type. E.g.,
907 /contenttype?text/css returns an html file with the Content-Type
908 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000909 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000910 return False
911 query_char = self.path.find('?')
912 content_type = self.path[query_char + 1:].strip()
913 if not content_type:
914 content_type = 'text/html'
915 self.send_response(200)
916 self.send_header('Content-Type', content_type)
917 self.end_headers()
918 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
919 return True
920
921 def ServerRedirectHandler(self):
922 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000923 '/server-redirect?http://foo.bar/asdf' to redirect to
924 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000925
926 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000927 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000928 return False
929
930 query_char = self.path.find('?')
931 if query_char < 0 or len(self.path) <= query_char + 1:
932 self.sendRedirectHelp(test_name)
933 return True
934 dest = self.path[query_char + 1:]
935
936 self.send_response(301) # moved permanently
937 self.send_header('Location', dest)
938 self.send_header('Content-type', 'text/html')
939 self.end_headers()
940 self.wfile.write('<html><head>')
941 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
942
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000943 return True
initial.commit94958cf2008-07-26 22:42:52 +0000944
945 def ClientRedirectHandler(self):
946 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000947 '/client-redirect?http://foo.bar/asdf' to redirect to
948 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000949
950 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000951 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000952 return False
953
954 query_char = self.path.find('?');
955 if query_char < 0 or len(self.path) <= query_char + 1:
956 self.sendRedirectHelp(test_name)
957 return True
958 dest = self.path[query_char + 1:]
959
960 self.send_response(200)
961 self.send_header('Content-type', 'text/html')
962 self.end_headers()
963 self.wfile.write('<html><head>')
964 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
965 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
966
967 return True
968
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +0000969 def ChromiumSyncTimeHandler(self):
970 """Handle Chromium sync .../time requests.
971
972 The syncer sometimes checks server reachability by examining /time.
973 """
974 test_name = "/chromiumsync/time"
975 if not self._ShouldHandleRequest(test_name):
976 return False
977
978 self.send_response(200)
979 self.send_header('Content-type', 'text/html')
980 self.end_headers()
981 return True
982
983 def ChromiumSyncCommandHandler(self):
984 """Handle a chromiumsync command arriving via http.
985
986 This covers all sync protocol commands: authentication, getupdates, and
987 commit.
988 """
989 test_name = "/chromiumsync/command"
990 if not self._ShouldHandleRequest(test_name):
991 return False
992
993 length = int(self.headers.getheader('content-length'))
994 raw_request = self.rfile.read(length)
995
pathorn@chromium.org44920122010-07-27 18:25:35 +0000996 if not self.server._sync_handler:
997 import chromiumsync
998 self.server._sync_handler = chromiumsync.TestServer()
999 http_response, raw_reply = self.server._sync_handler.HandleCommand(
nick@chromium.org06b8a662010-09-22 22:50:18 +00001000 self.path, raw_request)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001001 self.send_response(http_response)
1002 self.end_headers()
1003 self.wfile.write(raw_reply)
1004 return True
1005
tony@chromium.org03266982010-03-05 03:18:42 +00001006 def MultipartHandler(self):
1007 """Send a multipart response (10 text/html pages)."""
1008 test_name = "/multipart"
1009 if not self._ShouldHandleRequest(test_name):
1010 return False
1011
1012 num_frames = 10
1013 bound = '12345'
1014 self.send_response(200)
1015 self.send_header('Content-type',
1016 'multipart/x-mixed-replace;boundary=' + bound)
1017 self.end_headers()
1018
1019 for i in xrange(num_frames):
1020 self.wfile.write('--' + bound + '\r\n')
1021 self.wfile.write('Content-type: text/html\r\n\r\n')
1022 self.wfile.write('<title>page ' + str(i) + '</title>')
1023 self.wfile.write('page ' + str(i))
1024
1025 self.wfile.write('--' + bound + '--')
1026 return True
1027
initial.commit94958cf2008-07-26 22:42:52 +00001028 def DefaultResponseHandler(self):
1029 """This is the catch-all response handler for requests that aren't handled
1030 by one of the special handlers above.
1031 Note that we specify the content-length as without it the https connection
1032 is not closed properly (and the browser keeps expecting data)."""
1033
1034 contents = "Default response given for path: " + self.path
1035 self.send_response(200)
1036 self.send_header('Content-type', 'text/html')
1037 self.send_header("Content-Length", len(contents))
1038 self.end_headers()
1039 self.wfile.write(contents)
1040 return True
1041
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001042 def RedirectConnectHandler(self):
1043 """Sends a redirect to the CONNECT request for www.redirect.com. This
1044 response is not specified by the RFC, so the browser should not follow
1045 the redirect."""
1046
1047 if (self.path.find("www.redirect.com") < 0):
1048 return False
1049
1050 dest = "http://www.destination.com/foo.js"
1051
1052 self.send_response(302) # moved temporarily
1053 self.send_header('Location', dest)
1054 self.send_header('Connection', 'close')
1055 self.end_headers()
1056 return True
1057
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001058 def ServerAuthConnectHandler(self):
1059 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1060 response doesn't make sense because the proxy server cannot request
1061 server authentication."""
1062
1063 if (self.path.find("www.server-auth.com") < 0):
1064 return False
1065
1066 challenge = 'Basic realm="WallyWorld"'
1067
1068 self.send_response(401) # unauthorized
1069 self.send_header('WWW-Authenticate', challenge)
1070 self.send_header('Connection', 'close')
1071 self.end_headers()
1072 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001073
1074 def DefaultConnectResponseHandler(self):
1075 """This is the catch-all response handler for CONNECT requests that aren't
1076 handled by one of the special handlers above. Real Web servers respond
1077 with 400 to CONNECT requests."""
1078
1079 contents = "Your client has issued a malformed or illegal request."
1080 self.send_response(400) # bad request
1081 self.send_header('Content-type', 'text/html')
1082 self.send_header("Content-Length", len(contents))
1083 self.end_headers()
1084 self.wfile.write(contents)
1085 return True
1086
1087 def do_CONNECT(self):
1088 for handler in self._connect_handlers:
1089 if handler():
1090 return
1091
initial.commit94958cf2008-07-26 22:42:52 +00001092 def do_GET(self):
1093 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001094 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001095 return
1096
1097 def do_POST(self):
1098 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001099 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001100 return
1101
ananta@chromium.org56d146f2010-01-11 19:03:01 +00001102 def do_PUT(self):
1103 for handler in self._put_handlers:
1104 if handler():
1105 return
1106
initial.commit94958cf2008-07-26 22:42:52 +00001107 # called by the redirect handling function when there is no parameter
1108 def sendRedirectHelp(self, redirect_name):
1109 self.send_response(200)
1110 self.send_header('Content-type', 'text/html')
1111 self.end_headers()
1112 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1113 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1114 self.wfile.write('</body></html>')
1115
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001116def MakeDataDir():
1117 if options.data_dir:
1118 if not os.path.isdir(options.data_dir):
1119 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1120 return None
1121 my_data_dir = options.data_dir
1122 else:
1123 # Create the default path to our data dir, relative to the exe dir.
1124 my_data_dir = os.path.dirname(sys.argv[0])
1125 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001126 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001127
1128 #TODO(ibrar): Must use Find* funtion defined in google\tools
1129 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1130
1131 return my_data_dir
1132
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001133class FileMultiplexer:
1134 def __init__(self, fd1, fd2) :
1135 self.__fd1 = fd1
1136 self.__fd2 = fd2
1137
1138 def __del__(self) :
1139 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1140 self.__fd1.close()
1141 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1142 self.__fd2.close()
1143
1144 def write(self, text) :
1145 self.__fd1.write(text)
1146 self.__fd2.write(text)
1147
1148 def flush(self) :
1149 self.__fd1.flush()
1150 self.__fd2.flush()
1151
initial.commit94958cf2008-07-26 22:42:52 +00001152def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001153 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001154 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1155 sys.stderr = FileMultiplexer(sys.stderr, logfile)
initial.commit94958cf2008-07-26 22:42:52 +00001156
1157 port = options.port
1158
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001159 if options.server_type == SERVER_HTTP:
1160 if options.cert:
1161 # let's make sure the cert file exists.
1162 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001163 print 'specified server cert file not found: ' + options.cert + \
1164 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001165 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001166 for ca_cert in options.ssl_client_ca:
1167 if not os.path.isfile(ca_cert):
1168 print 'specified trusted client CA file not found: ' + ca_cert + \
1169 ' exiting...'
1170 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001171 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001172 options.ssl_client_auth, options.ssl_client_ca)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001173 print 'HTTPS server started on port %d...' % port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001174 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001175 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001176 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001177
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001178 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001179 server.file_root_url = options.file_root_url
pathorn@chromium.org44920122010-07-27 18:25:35 +00001180 server._sync_handler = None
nick@chromium.org8daefd62010-10-27 23:24:05 +00001181
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001182 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001183 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001184 my_data_dir = MakeDataDir()
1185
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001186 # Instantiate a dummy authorizer for managing 'virtual' users
1187 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1188
1189 # Define a new user having full r/w permissions and a read-only
1190 # anonymous user
1191 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1192
1193 authorizer.add_anonymous(my_data_dir)
1194
1195 # Instantiate FTP handler class
1196 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1197 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001198
1199 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001200 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1201 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001202
1203 # Instantiate FTP server class and listen to 127.0.0.1:port
1204 address = ('127.0.0.1', port)
1205 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
nick@chromium.org8daefd62010-10-27 23:24:05 +00001206 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001207
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001208 # Notify the parent that we've started. (BaseServer subclasses
1209 # bind their sockets on construction.)
1210 if options.startup_pipe is not None:
1211 if sys.platform == 'win32':
1212 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1213 else:
1214 fd = options.startup_pipe
1215 startup_pipe = os.fdopen(fd, "w")
nick@chromium.org8daefd62010-10-27 23:24:05 +00001216 startup_pipe.write("READY")
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001217 startup_pipe.close()
1218
initial.commit94958cf2008-07-26 22:42:52 +00001219 try:
1220 server.serve_forever()
1221 except KeyboardInterrupt:
1222 print 'shutting down server'
1223 server.stop = True
1224
1225if __name__ == '__main__':
1226 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001227 option_parser.add_option("-f", '--ftp', action='store_const',
1228 const=SERVER_FTP, default=SERVER_HTTP,
1229 dest='server_type',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001230 help='FTP or HTTP server: default is HTTP.')
nick@chromium.org8daefd62010-10-27 23:24:05 +00001231 option_parser.add_option('', '--port', default='8888', type='int',
1232 help='Port used by the server.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001233 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001234 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001235 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001236 help='Specify that https should be used, specify '
1237 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001238 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001239 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1240 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001241 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1242 help='Specify that the client certificate request '
1243 'should indicate that it supports the CA contained '
1244 'in the specified certificate file')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001245 option_parser.add_option('', '--file-root-url', default='/files/',
1246 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001247 option_parser.add_option('', '--startup-pipe', type='int',
1248 dest='startup_pipe',
1249 help='File handle of pipe to parent process')
initial.commit94958cf2008-07-26 22:42:52 +00001250 options, args = option_parser.parse_args()
1251
1252 sys.exit(main(options, args))