blob: 98d5bbc00c76c4addbc870525b4791409d27d322 [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
license.botf3378c22008-08-24 00:55:55 +00002# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
3# 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
25import tlslite
26import tlslite.api
erikkay@google.comd5182ff2009-01-08 20:45:27 +000027import pyftpdlib.ftpserver
28
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000029try:
30 import hashlib
31 _new_md5 = hashlib.md5
32except ImportError:
33 import md5
34 _new_md5 = md5.new
35
maruel@chromium.org756cf982009-03-05 12:46:38 +000036SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000037SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000038
39debug_output = sys.stderr
40def debug(str):
41 debug_output.write(str + "\n")
42 debug_output.flush()
43
44class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
45 """This is a specialization of of BaseHTTPServer to allow it
46 to be exited cleanly (by setting its "stop" member to True)."""
47
48 def serve_forever(self):
49 self.stop = False
50 self.nonce = None
51 while not self.stop:
52 self.handle_request()
53 self.socket.close()
54
55class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
56 """This is a specialization of StoppableHTTPerver that add https support."""
57
58 def __init__(self, server_address, request_hander_class, cert_path):
59 s = open(cert_path).read()
60 x509 = tlslite.api.X509()
61 x509.parse(s)
62 self.cert_chain = tlslite.api.X509CertChain([x509])
63 s = open(cert_path).read()
64 self.private_key = tlslite.api.parsePEMKey(s, private=True)
65
66 self.session_cache = tlslite.api.SessionCache()
67 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
68
69 def handshake(self, tlsConnection):
70 """Creates the SSL connection."""
71 try:
72 tlsConnection.handshakeServer(certChain=self.cert_chain,
73 privateKey=self.private_key,
74 sessionCache=self.session_cache)
75 tlsConnection.ignoreAbruptClose = True
76 return True
77 except tlslite.api.TLSError, error:
78 print "Handshake failure:", str(error)
79 return False
80
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +000081class ForkingHTTPServer(SocketServer.ForkingMixIn, StoppableHTTPServer):
82 """This is a specialization of of StoppableHTTPServer which serves each
83 request in a separate process"""
84 pass
85
86class ForkingHTTPSServer(SocketServer.ForkingMixIn, HTTPSServer):
87 """This is a specialization of of HTTPSServer which serves each
88 request in a separate process"""
89 pass
90
initial.commit94958cf2008-07-26 22:42:52 +000091class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
92
93 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000094 self._connect_handlers = [
95 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000096 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000097 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +000098 self._get_handlers = [
99 self.KillHandler,
100 self.NoCacheMaxAgeTimeHandler,
101 self.NoCacheTimeHandler,
102 self.CacheTimeHandler,
103 self.CacheExpiresHandler,
104 self.CacheProxyRevalidateHandler,
105 self.CachePrivateHandler,
106 self.CachePublicHandler,
107 self.CacheSMaxAgeHandler,
108 self.CacheMustRevalidateHandler,
109 self.CacheMustRevalidateMaxAgeHandler,
110 self.CacheNoStoreHandler,
111 self.CacheNoStoreMaxAgeHandler,
112 self.CacheNoTransformHandler,
113 self.DownloadHandler,
114 self.DownloadFinishHandler,
115 self.EchoHeader,
ananta@chromium.org219b2062009-10-23 16:09:41 +0000116 self.EchoHeaderOverride,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000117 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000118 self.FileHandler,
119 self.RealFileWithCommonHeaderHandler,
120 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000121 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000122 self.AuthBasicHandler,
123 self.AuthDigestHandler,
124 self.SlowServerHandler,
125 self.ContentTypeHandler,
126 self.ServerRedirectHandler,
127 self.ClientRedirectHandler,
128 self.DefaultResponseHandler]
129 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000130 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000131 self.EchoTitleHandler,
132 self.EchoAllHandler,
133 self.EchoHandler] + self._get_handlers
134
maruel@google.come250a9b2009-03-10 17:39:46 +0000135 self._mime_types = {
136 'gif': 'image/gif',
137 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000138 'jpg' : 'image/jpeg',
139 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000140 }
initial.commit94958cf2008-07-26 22:42:52 +0000141 self._default_mime_type = 'text/html'
142
maruel@google.come250a9b2009-03-10 17:39:46 +0000143 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
144 client_address,
145 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000146
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000147 def _ShouldHandleRequest(self, handler_name):
148 """Determines if the path can be handled by the handler.
149
150 We consider a handler valid if the path begins with the
151 handler name. It can optionally be followed by "?*", "/*".
152 """
153
154 pattern = re.compile('%s($|\?|/).*' % handler_name)
155 return pattern.match(self.path)
156
initial.commit94958cf2008-07-26 22:42:52 +0000157 def GetMIMETypeFromName(self, file_name):
158 """Returns the mime type for the specified file_name. So far it only looks
159 at the file extension."""
160
161 (shortname, extension) = os.path.splitext(file_name)
162 if len(extension) == 0:
163 # no extension.
164 return self._default_mime_type
165
ericroman@google.comc17ca532009-05-07 03:51:05 +0000166 # extension starts with a dot, so we need to remove it
167 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000168
169 def KillHandler(self):
170 """This request handler kills the server, for use when we're done"
171 with the a particular test."""
172
173 if (self.path.find("kill") < 0):
174 return False
175
176 self.send_response(200)
177 self.send_header('Content-type', 'text/html')
178 self.send_header('Cache-Control', 'max-age=0')
179 self.end_headers()
180 self.wfile.write("Time to die")
181 self.server.stop = True
182
183 return True
184
185 def NoCacheMaxAgeTimeHandler(self):
186 """This request handler yields a page with the title set to the current
187 system time, and no caching requested."""
188
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000189 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000190 return False
191
192 self.send_response(200)
193 self.send_header('Cache-Control', 'max-age=0')
194 self.send_header('Content-type', 'text/html')
195 self.end_headers()
196
maruel@google.come250a9b2009-03-10 17:39:46 +0000197 self.wfile.write('<html><head><title>%s</title></head></html>' %
198 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000199
200 return True
201
202 def NoCacheTimeHandler(self):
203 """This request handler yields a page with the title set to the current
204 system time, and no caching requested."""
205
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000206 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000207 return False
208
209 self.send_response(200)
210 self.send_header('Cache-Control', 'no-cache')
211 self.send_header('Content-type', 'text/html')
212 self.end_headers()
213
maruel@google.come250a9b2009-03-10 17:39:46 +0000214 self.wfile.write('<html><head><title>%s</title></head></html>' %
215 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000216
217 return True
218
219 def CacheTimeHandler(self):
220 """This request handler yields a page with the title set to the current
221 system time, and allows caching for one minute."""
222
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000223 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000224 return False
225
226 self.send_response(200)
227 self.send_header('Cache-Control', 'max-age=60')
228 self.send_header('Content-type', 'text/html')
229 self.end_headers()
230
maruel@google.come250a9b2009-03-10 17:39:46 +0000231 self.wfile.write('<html><head><title>%s</title></head></html>' %
232 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000233
234 return True
235
236 def CacheExpiresHandler(self):
237 """This request handler yields a page with the title set to the current
238 system time, and set the page to expire on 1 Jan 2099."""
239
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000240 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000241 return False
242
243 self.send_response(200)
244 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
245 self.send_header('Content-type', 'text/html')
246 self.end_headers()
247
maruel@google.come250a9b2009-03-10 17:39:46 +0000248 self.wfile.write('<html><head><title>%s</title></head></html>' %
249 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000250
251 return True
252
253 def CacheProxyRevalidateHandler(self):
254 """This request handler yields a page with the title set to the current
255 system time, and allows caching for 60 seconds"""
256
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000257 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000258 return False
259
260 self.send_response(200)
261 self.send_header('Content-type', 'text/html')
262 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
263 self.end_headers()
264
maruel@google.come250a9b2009-03-10 17:39:46 +0000265 self.wfile.write('<html><head><title>%s</title></head></html>' %
266 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000267
268 return True
269
270 def CachePrivateHandler(self):
271 """This request handler yields a page with the title set to the current
272 system time, and allows caching for 5 seconds."""
273
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000274 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000275 return False
276
277 self.send_response(200)
278 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000279 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000280 self.end_headers()
281
maruel@google.come250a9b2009-03-10 17:39:46 +0000282 self.wfile.write('<html><head><title>%s</title></head></html>' %
283 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000284
285 return True
286
287 def CachePublicHandler(self):
288 """This request handler yields a page with the title set to the current
289 system time, and allows caching for 5 seconds."""
290
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000291 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000292 return False
293
294 self.send_response(200)
295 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000296 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000297 self.end_headers()
298
maruel@google.come250a9b2009-03-10 17:39:46 +0000299 self.wfile.write('<html><head><title>%s</title></head></html>' %
300 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000301
302 return True
303
304 def CacheSMaxAgeHandler(self):
305 """This request handler yields a page with the title set to the current
306 system time, and does not allow for caching."""
307
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000308 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000309 return False
310
311 self.send_response(200)
312 self.send_header('Content-type', 'text/html')
313 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
314 self.end_headers()
315
maruel@google.come250a9b2009-03-10 17:39:46 +0000316 self.wfile.write('<html><head><title>%s</title></head></html>' %
317 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000318
319 return True
320
321 def CacheMustRevalidateHandler(self):
322 """This request handler yields a page with the title set to the current
323 system time, and does not allow caching."""
324
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000325 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000326 return False
327
328 self.send_response(200)
329 self.send_header('Content-type', 'text/html')
330 self.send_header('Cache-Control', 'must-revalidate')
331 self.end_headers()
332
maruel@google.come250a9b2009-03-10 17:39:46 +0000333 self.wfile.write('<html><head><title>%s</title></head></html>' %
334 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000335
336 return True
337
338 def CacheMustRevalidateMaxAgeHandler(self):
339 """This request handler yields a page with the title set to the current
340 system time, and does not allow caching event though max-age of 60
341 seconds is specified."""
342
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000343 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000344 return False
345
346 self.send_response(200)
347 self.send_header('Content-type', 'text/html')
348 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
349 self.end_headers()
350
maruel@google.come250a9b2009-03-10 17:39:46 +0000351 self.wfile.write('<html><head><title>%s</title></head></html>' %
352 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000353
354 return True
355
initial.commit94958cf2008-07-26 22:42:52 +0000356 def CacheNoStoreHandler(self):
357 """This request handler yields a page with the title set to the current
358 system time, and does not allow the page to be stored."""
359
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000360 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000361 return False
362
363 self.send_response(200)
364 self.send_header('Content-type', 'text/html')
365 self.send_header('Cache-Control', 'no-store')
366 self.end_headers()
367
maruel@google.come250a9b2009-03-10 17:39:46 +0000368 self.wfile.write('<html><head><title>%s</title></head></html>' %
369 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000370
371 return True
372
373 def CacheNoStoreMaxAgeHandler(self):
374 """This request handler yields a page with the title set to the current
375 system time, and does not allow the page to be stored even though max-age
376 of 60 seconds is specified."""
377
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000378 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000379 return False
380
381 self.send_response(200)
382 self.send_header('Content-type', 'text/html')
383 self.send_header('Cache-Control', 'max-age=60, no-store')
384 self.end_headers()
385
maruel@google.come250a9b2009-03-10 17:39:46 +0000386 self.wfile.write('<html><head><title>%s</title></head></html>' %
387 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000388
389 return True
390
391
392 def CacheNoTransformHandler(self):
393 """This request handler yields a page with the title set to the current
394 system time, and does not allow the content to transformed during
395 user-agent caching"""
396
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000397 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000398 return False
399
400 self.send_response(200)
401 self.send_header('Content-type', 'text/html')
402 self.send_header('Cache-Control', 'no-transform')
403 self.end_headers()
404
maruel@google.come250a9b2009-03-10 17:39:46 +0000405 self.wfile.write('<html><head><title>%s</title></head></html>' %
406 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000407
408 return True
409
410 def EchoHeader(self):
411 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000412 """The only difference between this function and the EchoHeaderOverride"""
413 """function is in the parameter being passed to the helper function"""
414 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000415
ananta@chromium.org219b2062009-10-23 16:09:41 +0000416 def EchoHeaderOverride(self):
417 """This handler echoes back the value of a specific request header."""
418 """The UrlRequest unit tests also execute for ChromeFrame which uses"""
419 """IE to issue HTTP requests using the host network stack."""
420 """The Accept and Charset tests which expect the server to echo back"""
421 """the corresponding headers fail here as IE returns cached responses"""
422 """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
423 """treats this request as a new request and does not cache it."""
424 return self.EchoHeaderHelper("/echoheaderoverride")
425
426 def EchoHeaderHelper(self, echo_header):
427 """This function echoes back the value of the request header passed in."""
428 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000429 return False
430
431 query_char = self.path.find('?')
432 if query_char != -1:
433 header_name = self.path[query_char+1:]
434
435 self.send_response(200)
436 self.send_header('Content-type', 'text/plain')
437 self.send_header('Cache-control', 'max-age=60000')
438 # insert a vary header to properly indicate that the cachability of this
439 # request is subject to value of the request header being echoed.
440 if len(header_name) > 0:
441 self.send_header('Vary', header_name)
442 self.end_headers()
443
444 if len(header_name) > 0:
445 self.wfile.write(self.headers.getheader(header_name))
446
447 return True
448
449 def EchoHandler(self):
450 """This handler just echoes back the payload of the request, for testing
451 form submission."""
452
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000453 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000454 return False
455
456 self.send_response(200)
457 self.send_header('Content-type', 'text/html')
458 self.end_headers()
459 length = int(self.headers.getheader('content-length'))
460 request = self.rfile.read(length)
461 self.wfile.write(request)
462 return True
463
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000464 def WriteFile(self):
465 """This is handler dumps the content of POST request to a disk file into
466 the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000467
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000468 prefix='/writefile/'
469 if not self.path.startswith(prefix):
470 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000471
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000472 file_name = self.path[len(prefix):]
473
474 # do not allow fancy chars in file name
475 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
476 if len(file_name) and file_name[0] != '.':
477 path = os.path.join(self.server.data_dir, 'dump', file_name);
478 length = int(self.headers.getheader('content-length'))
479 request = self.rfile.read(length)
480 f = open(path, "wb")
481 f.write(request);
482 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000483
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000484 self.send_response(200)
485 self.send_header('Content-type', 'text/html')
486 self.end_headers()
487 self.wfile.write('<html>%s</html>' % file_name)
488 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000489
initial.commit94958cf2008-07-26 22:42:52 +0000490 def EchoTitleHandler(self):
491 """This handler is like Echo, but sets the page title to the request."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
497 self.send_header('Content-type', 'text/html')
498 self.end_headers()
499 length = int(self.headers.getheader('content-length'))
500 request = self.rfile.read(length)
501 self.wfile.write('<html><head><title>')
502 self.wfile.write(request)
503 self.wfile.write('</title></head></html>')
504 return True
505
506 def EchoAllHandler(self):
507 """This handler yields a (more) human-readable page listing information
508 about the request header & contents."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
514 self.send_header('Content-type', 'text/html')
515 self.end_headers()
516 self.wfile.write('<html><head><style>'
517 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
518 '</style></head><body>'
519 '<div style="float: right">'
520 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
521 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000522
ericroman@google.coma47622b2008-11-15 04:36:51 +0000523 if self.command == 'POST':
524 length = int(self.headers.getheader('content-length'))
525 qs = self.rfile.read(length)
526 params = cgi.parse_qs(qs, keep_blank_values=1)
527
528 for param in params:
529 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000530
531 self.wfile.write('</pre>')
532
533 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
534
535 self.wfile.write('</body></html>')
536 return True
537
538 def DownloadHandler(self):
539 """This handler sends a downloadable file with or without reporting
540 the size (6K)."""
541
542 if self.path.startswith("/download-unknown-size"):
543 send_length = False
544 elif self.path.startswith("/download-known-size"):
545 send_length = True
546 else:
547 return False
548
549 #
550 # The test which uses this functionality is attempting to send
551 # small chunks of data to the client. Use a fairly large buffer
552 # so that we'll fill chrome's IO buffer enough to force it to
553 # actually write the data.
554 # See also the comments in the client-side of this test in
555 # download_uitest.cc
556 #
557 size_chunk1 = 35*1024
558 size_chunk2 = 10*1024
559
560 self.send_response(200)
561 self.send_header('Content-type', 'application/octet-stream')
562 self.send_header('Cache-Control', 'max-age=0')
563 if send_length:
564 self.send_header('Content-Length', size_chunk1 + size_chunk2)
565 self.end_headers()
566
567 # First chunk of data:
568 self.wfile.write("*" * size_chunk1)
569 self.wfile.flush()
570
571 # handle requests until one of them clears this flag.
572 self.server.waitForDownload = True
573 while self.server.waitForDownload:
574 self.server.handle_request()
575
576 # Second chunk of data:
577 self.wfile.write("*" * size_chunk2)
578 return True
579
580 def DownloadFinishHandler(self):
581 """This handler just tells the server to finish the current download."""
582
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000583 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000584 return False
585
586 self.server.waitForDownload = False
587 self.send_response(200)
588 self.send_header('Content-type', 'text/html')
589 self.send_header('Cache-Control', 'max-age=0')
590 self.end_headers()
591 return True
592
593 def FileHandler(self):
594 """This handler sends the contents of the requested file. Wow, it's like
595 a real webserver!"""
596
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000597 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000598 if not self.path.startswith(prefix):
599 return False
600
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000601 # Consume a request body if present.
602 if self.command == 'POST':
603 self.rfile.read(int(self.headers.getheader('content-length')))
604
initial.commit94958cf2008-07-26 22:42:52 +0000605 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000606 if file.find('?') > -1:
607 # Ignore the query parameters entirely.
608 url, querystring = file.split('?')
609 else:
610 url = file
611 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000612 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000613 if os.path.isdir(path):
614 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000615
616 if not os.path.isfile(path):
617 print "File not found " + file + " full path:" + path
618 self.send_error(404)
619 return True
620
621 f = open(path, "rb")
622 data = f.read()
623 f.close()
624
625 # If file.mock-http-headers exists, it contains the headers we
626 # should send. Read them in and parse them.
627 headers_path = path + '.mock-http-headers'
628 if os.path.isfile(headers_path):
629 f = open(headers_path, "r")
630
631 # "HTTP/1.1 200 OK"
632 response = f.readline()
633 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
634 self.send_response(int(status_code))
635
636 for line in f:
637 # "name: value"
638 name, value = re.findall('(\S+):\s*(.*)', line)[0]
639 self.send_header(name, value)
640 f.close()
641 else:
642 # Could be more generic once we support mime-type sniffing, but for
643 # now we need to set it explicitly.
644 self.send_response(200)
645 self.send_header('Content-type', self.GetMIMETypeFromName(file))
646 self.send_header('Content-Length', len(data))
647 self.end_headers()
648
649 self.wfile.write(data)
650
651 return True
652
653 def RealFileWithCommonHeaderHandler(self):
654 """This handler sends the contents of the requested file without the pseudo
655 http head!"""
656
657 prefix='/realfiles/'
658 if not self.path.startswith(prefix):
659 return False
660
661 file = self.path[len(prefix):]
662 path = os.path.join(self.server.data_dir, file)
663
664 try:
665 f = open(path, "rb")
666 data = f.read()
667 f.close()
668
669 # just simply set the MIME as octal stream
670 self.send_response(200)
671 self.send_header('Content-type', 'application/octet-stream')
672 self.end_headers()
673
674 self.wfile.write(data)
675 except:
676 self.send_error(404)
677
678 return True
679
680 def RealBZ2FileWithCommonHeaderHandler(self):
681 """This handler sends the bzip2 contents of the requested file with
682 corresponding Content-Encoding field in http head!"""
683
684 prefix='/realbz2files/'
685 if not self.path.startswith(prefix):
686 return False
687
688 parts = self.path.split('?')
689 file = parts[0][len(prefix):]
690 path = os.path.join(self.server.data_dir, file) + '.bz2'
691
692 if len(parts) > 1:
693 options = parts[1]
694 else:
695 options = ''
696
697 try:
698 self.send_response(200)
699 accept_encoding = self.headers.get("Accept-Encoding")
700 if accept_encoding.find("bzip2") != -1:
701 f = open(path, "rb")
702 data = f.read()
703 f.close()
704 self.send_header('Content-Encoding', 'bzip2')
705 self.send_header('Content-type', 'application/x-bzip2')
706 self.end_headers()
707 if options == 'incremental-header':
708 self.wfile.write(data[:1])
709 self.wfile.flush()
710 time.sleep(1.0)
711 self.wfile.write(data[1:])
712 else:
713 self.wfile.write(data)
714 else:
715 """client do not support bzip2 format, send pseudo content
716 """
717 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
718 self.end_headers()
719 self.wfile.write("you do not support bzip2 encoding")
720 except:
721 self.send_error(404)
722
723 return True
724
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000725 def SetCookieHandler(self):
726 """This handler just sets a cookie, for testing cookie handling."""
727
728 if not self._ShouldHandleRequest("/set-cookie"):
729 return False
730
731 query_char = self.path.find('?')
732 if query_char != -1:
733 cookie_values = self.path[query_char + 1:].split('&')
734 else:
735 cookie_values = ("",)
736 self.send_response(200)
737 self.send_header('Content-type', 'text/html')
738 for cookie_value in cookie_values:
739 self.send_header('Set-Cookie', '%s' % cookie_value)
740 self.end_headers()
741 for cookie_value in cookie_values:
742 self.wfile.write('%s' % cookie_value)
743 return True
744
initial.commit94958cf2008-07-26 22:42:52 +0000745 def AuthBasicHandler(self):
746 """This handler tests 'Basic' authentication. It just sends a page with
747 title 'user/pass' if you succeed."""
748
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000749 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000750 return False
751
752 username = userpass = password = b64str = ""
753
ericroman@google.com239b4d82009-03-27 04:00:22 +0000754 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
755
initial.commit94958cf2008-07-26 22:42:52 +0000756 auth = self.headers.getheader('authorization')
757 try:
758 if not auth:
759 raise Exception('no auth')
760 b64str = re.findall(r'Basic (\S+)', auth)[0]
761 userpass = base64.b64decode(b64str)
762 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
763 if password != 'secret':
764 raise Exception('wrong password')
765 except Exception, e:
766 # Authentication failed.
767 self.send_response(401)
768 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
769 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000770 if set_cookie_if_challenged:
771 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000772 self.end_headers()
773 self.wfile.write('<html><head>')
774 self.wfile.write('<title>Denied: %s</title>' % e)
775 self.wfile.write('</head><body>')
776 self.wfile.write('auth=%s<p>' % auth)
777 self.wfile.write('b64str=%s<p>' % b64str)
778 self.wfile.write('username: %s<p>' % username)
779 self.wfile.write('userpass: %s<p>' % userpass)
780 self.wfile.write('password: %s<p>' % password)
781 self.wfile.write('You sent:<br>%s<p>' % self.headers)
782 self.wfile.write('</body></html>')
783 return True
784
785 # Authentication successful. (Return a cachable response to allow for
786 # testing cached pages that require authentication.)
787 if_none_match = self.headers.getheader('if-none-match')
788 if if_none_match == "abc":
789 self.send_response(304)
790 self.end_headers()
791 else:
792 self.send_response(200)
793 self.send_header('Content-type', 'text/html')
794 self.send_header('Cache-control', 'max-age=60000')
795 self.send_header('Etag', 'abc')
796 self.end_headers()
797 self.wfile.write('<html><head>')
798 self.wfile.write('<title>%s/%s</title>' % (username, password))
799 self.wfile.write('</head><body>')
800 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000801 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000802 self.wfile.write('</body></html>')
803
804 return True
805
806 def AuthDigestHandler(self):
807 """This handler tests 'Digest' authentication. It just sends a page with
808 title 'user/pass' if you succeed."""
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
813 # Periodically generate a new nonce. Technically we should incorporate
814 # the request URL into this, but we don't care for testing.
815 nonce_life = 10
816 stale = False
maruel@google.come250a9b2009-03-10 17:39:46 +0000817 if (not self.server.nonce or
818 (time.time() - self.server.nonce_time > nonce_life)):
initial.commit94958cf2008-07-26 22:42:52 +0000819 if self.server.nonce:
820 stale = True
821 self.server.nonce_time = time.time()
822 self.server.nonce = \
maruel@google.come250a9b2009-03-10 17:39:46 +0000823 _new_md5(time.ctime(self.server.nonce_time) +
824 'privatekey').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000825
826 nonce = self.server.nonce
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000827 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000828 password = 'secret'
829 realm = 'testrealm'
830
831 auth = self.headers.getheader('authorization')
832 pairs = {}
833 try:
834 if not auth:
835 raise Exception('no auth')
836 if not auth.startswith('Digest'):
837 raise Exception('not digest')
838 # Pull out all the name="value" pairs as a dictionary.
839 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
840
841 # Make sure it's all valid.
842 if pairs['nonce'] != nonce:
843 raise Exception('wrong nonce')
844 if pairs['opaque'] != opaque:
845 raise Exception('wrong opaque')
846
847 # Check the 'response' value and make sure it matches our magic hash.
848 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000849 hash_a1 = _new_md5(
850 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000851 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000852 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000853 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000854 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
855 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000856 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000857
858 if pairs['response'] != response:
859 raise Exception('wrong password')
860 except Exception, e:
861 # Authentication failed.
862 self.send_response(401)
863 hdr = ('Digest '
864 'realm="%s", '
865 'domain="/", '
866 'qop="auth", '
867 'algorithm=MD5, '
868 'nonce="%s", '
869 'opaque="%s"') % (realm, nonce, opaque)
870 if stale:
871 hdr += ', stale="TRUE"'
872 self.send_header('WWW-Authenticate', hdr)
873 self.send_header('Content-type', 'text/html')
874 self.end_headers()
875 self.wfile.write('<html><head>')
876 self.wfile.write('<title>Denied: %s</title>' % e)
877 self.wfile.write('</head><body>')
878 self.wfile.write('auth=%s<p>' % auth)
879 self.wfile.write('pairs=%s<p>' % pairs)
880 self.wfile.write('You sent:<br>%s<p>' % self.headers)
881 self.wfile.write('We are replying:<br>%s<p>' % hdr)
882 self.wfile.write('</body></html>')
883 return True
884
885 # Authentication successful.
886 self.send_response(200)
887 self.send_header('Content-type', 'text/html')
888 self.end_headers()
889 self.wfile.write('<html><head>')
890 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
891 self.wfile.write('</head><body>')
892 self.wfile.write('auth=%s<p>' % auth)
893 self.wfile.write('pairs=%s<p>' % pairs)
894 self.wfile.write('</body></html>')
895
896 return True
897
898 def SlowServerHandler(self):
899 """Wait for the user suggested time before responding. The syntax is
900 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000901 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000902 return False
903 query_char = self.path.find('?')
904 wait_sec = 1.0
905 if query_char >= 0:
906 try:
907 wait_sec = int(self.path[query_char + 1:])
908 except ValueError:
909 pass
910 time.sleep(wait_sec)
911 self.send_response(200)
912 self.send_header('Content-type', 'text/plain')
913 self.end_headers()
914 self.wfile.write("waited %d seconds" % wait_sec)
915 return True
916
917 def ContentTypeHandler(self):
918 """Returns a string of html with the given content type. E.g.,
919 /contenttype?text/css returns an html file with the Content-Type
920 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000921 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000922 return False
923 query_char = self.path.find('?')
924 content_type = self.path[query_char + 1:].strip()
925 if not content_type:
926 content_type = 'text/html'
927 self.send_response(200)
928 self.send_header('Content-Type', content_type)
929 self.end_headers()
930 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
931 return True
932
933 def ServerRedirectHandler(self):
934 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000935 '/server-redirect?http://foo.bar/asdf' to redirect to
936 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000937
938 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000939 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000940 return False
941
942 query_char = self.path.find('?')
943 if query_char < 0 or len(self.path) <= query_char + 1:
944 self.sendRedirectHelp(test_name)
945 return True
946 dest = self.path[query_char + 1:]
947
948 self.send_response(301) # moved permanently
949 self.send_header('Location', dest)
950 self.send_header('Content-type', 'text/html')
951 self.end_headers()
952 self.wfile.write('<html><head>')
953 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
954
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000955 return True
initial.commit94958cf2008-07-26 22:42:52 +0000956
957 def ClientRedirectHandler(self):
958 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000959 '/client-redirect?http://foo.bar/asdf' to redirect to
960 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000961
962 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000963 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000964 return False
965
966 query_char = self.path.find('?');
967 if query_char < 0 or len(self.path) <= query_char + 1:
968 self.sendRedirectHelp(test_name)
969 return True
970 dest = self.path[query_char + 1:]
971
972 self.send_response(200)
973 self.send_header('Content-type', 'text/html')
974 self.end_headers()
975 self.wfile.write('<html><head>')
976 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
977 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
978
979 return True
980
981 def DefaultResponseHandler(self):
982 """This is the catch-all response handler for requests that aren't handled
983 by one of the special handlers above.
984 Note that we specify the content-length as without it the https connection
985 is not closed properly (and the browser keeps expecting data)."""
986
987 contents = "Default response given for path: " + self.path
988 self.send_response(200)
989 self.send_header('Content-type', 'text/html')
990 self.send_header("Content-Length", len(contents))
991 self.end_headers()
992 self.wfile.write(contents)
993 return True
994
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000995 def RedirectConnectHandler(self):
996 """Sends a redirect to the CONNECT request for www.redirect.com. This
997 response is not specified by the RFC, so the browser should not follow
998 the redirect."""
999
1000 if (self.path.find("www.redirect.com") < 0):
1001 return False
1002
1003 dest = "http://www.destination.com/foo.js"
1004
1005 self.send_response(302) # moved temporarily
1006 self.send_header('Location', dest)
1007 self.send_header('Connection', 'close')
1008 self.end_headers()
1009 return True
1010
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001011 def ServerAuthConnectHandler(self):
1012 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1013 response doesn't make sense because the proxy server cannot request
1014 server authentication."""
1015
1016 if (self.path.find("www.server-auth.com") < 0):
1017 return False
1018
1019 challenge = 'Basic realm="WallyWorld"'
1020
1021 self.send_response(401) # unauthorized
1022 self.send_header('WWW-Authenticate', challenge)
1023 self.send_header('Connection', 'close')
1024 self.end_headers()
1025 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001026
1027 def DefaultConnectResponseHandler(self):
1028 """This is the catch-all response handler for CONNECT requests that aren't
1029 handled by one of the special handlers above. Real Web servers respond
1030 with 400 to CONNECT requests."""
1031
1032 contents = "Your client has issued a malformed or illegal request."
1033 self.send_response(400) # bad request
1034 self.send_header('Content-type', 'text/html')
1035 self.send_header("Content-Length", len(contents))
1036 self.end_headers()
1037 self.wfile.write(contents)
1038 return True
1039
1040 def do_CONNECT(self):
1041 for handler in self._connect_handlers:
1042 if handler():
1043 return
1044
initial.commit94958cf2008-07-26 22:42:52 +00001045 def do_GET(self):
1046 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001047 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001048 return
1049
1050 def do_POST(self):
1051 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001052 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001053 return
1054
1055 # called by the redirect handling function when there is no parameter
1056 def sendRedirectHelp(self, redirect_name):
1057 self.send_response(200)
1058 self.send_header('Content-type', 'text/html')
1059 self.end_headers()
1060 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1061 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1062 self.wfile.write('</body></html>')
1063
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001064def MakeDumpDir(data_dir):
1065 """Create directory named 'dump' where uploaded data via HTTP POST request
1066 will be stored. If the directory already exists all files and subdirectories
1067 will be deleted."""
1068 dump_dir = os.path.join(data_dir, 'dump');
1069 if os.path.isdir(dump_dir):
1070 shutil.rmtree(dump_dir)
1071 os.mkdir(dump_dir)
1072
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001073def MakeDataDir():
1074 if options.data_dir:
1075 if not os.path.isdir(options.data_dir):
1076 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1077 return None
1078 my_data_dir = options.data_dir
1079 else:
1080 # Create the default path to our data dir, relative to the exe dir.
1081 my_data_dir = os.path.dirname(sys.argv[0])
1082 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1083 "test", "data")
1084
1085 #TODO(ibrar): Must use Find* funtion defined in google\tools
1086 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1087
1088 return my_data_dir
1089
initial.commit94958cf2008-07-26 22:42:52 +00001090def main(options, args):
1091 # redirect output to a log file so it doesn't spam the unit test output
1092 logfile = open('testserver.log', 'w')
1093 sys.stderr = sys.stdout = logfile
1094
1095 port = options.port
1096
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001097 if options.server_type == SERVER_HTTP:
1098 if options.cert:
1099 # let's make sure the cert file exists.
1100 if not os.path.isfile(options.cert):
1101 print 'specified cert file not found: ' + options.cert + ' exiting...'
1102 return
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001103 if options.forking:
1104 server_class = ForkingHTTPSServer
1105 else:
1106 server_class = HTTPSServer
1107 server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001108 print 'HTTPS server started on port %d...' % port
1109 else:
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001110 if options.forking:
1111 server_class = ForkingHTTPServer
1112 else:
1113 server_class = StoppableHTTPServer
1114 server = server_class(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001115 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001116
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001117 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001118 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001119 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001120
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001121 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001122 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001123 my_data_dir = MakeDataDir()
1124
1125 def line_logger(msg):
1126 if (msg.find("kill") >= 0):
1127 server.stop = True
1128 print 'shutting down server'
1129 sys.exit(0)
1130
1131 # Instantiate a dummy authorizer for managing 'virtual' users
1132 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1133
1134 # Define a new user having full r/w permissions and a read-only
1135 # anonymous user
1136 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1137
1138 authorizer.add_anonymous(my_data_dir)
1139
1140 # Instantiate FTP handler class
1141 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1142 ftp_handler.authorizer = authorizer
1143 pyftpdlib.ftpserver.logline = line_logger
1144
1145 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001146 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1147 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001148
1149 # Instantiate FTP server class and listen to 127.0.0.1:port
1150 address = ('127.0.0.1', port)
1151 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1152 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001153
1154 try:
1155 server.serve_forever()
1156 except KeyboardInterrupt:
1157 print 'shutting down server'
1158 server.stop = True
1159
1160if __name__ == '__main__':
1161 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001162 option_parser.add_option("-f", '--ftp', action='store_const',
1163 const=SERVER_FTP, default=SERVER_HTTP,
1164 dest='server_type',
1165 help='FTP or HTTP server default HTTP')
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001166 option_parser.add_option('--forking', action='store_true', default=False,
1167 dest='forking',
1168 help='Serve each request in a separate process')
initial.commit94958cf2008-07-26 22:42:52 +00001169 option_parser.add_option('', '--port', default='8888', type='int',
1170 help='Port used by the server')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001171 option_parser.add_option('', '--data-dir', dest='data_dir',
initial.commit94958cf2008-07-26 22:42:52 +00001172 help='Directory from which to read the files')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001173 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001174 help='Specify that https should be used, specify '
1175 'the path to the cert containing the private key '
1176 'the server should use')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001177 option_parser.add_option('', '--file-root-url', default='/files/',
1178 help='Specify a root URL for files served.')
initial.commit94958cf2008-07-26 22:42:52 +00001179 options, args = option_parser.parse_args()
1180
1181 sys.exit(main(options, args))