blob: b80a939ff840209dd09aad328d33a60dd5a74334 [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,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000116 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000117 self.FileHandler,
118 self.RealFileWithCommonHeaderHandler,
119 self.RealBZ2FileWithCommonHeaderHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000120 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000121 self.AuthBasicHandler,
122 self.AuthDigestHandler,
123 self.SlowServerHandler,
124 self.ContentTypeHandler,
125 self.ServerRedirectHandler,
126 self.ClientRedirectHandler,
127 self.DefaultResponseHandler]
128 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000129 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000130 self.EchoTitleHandler,
131 self.EchoAllHandler,
132 self.EchoHandler] + self._get_handlers
133
maruel@google.come250a9b2009-03-10 17:39:46 +0000134 self._mime_types = {
135 'gif': 'image/gif',
136 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000137 'jpg' : 'image/jpeg',
138 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000139 }
initial.commit94958cf2008-07-26 22:42:52 +0000140 self._default_mime_type = 'text/html'
141
maruel@google.come250a9b2009-03-10 17:39:46 +0000142 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
143 client_address,
144 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000145
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000146 def _ShouldHandleRequest(self, handler_name):
147 """Determines if the path can be handled by the handler.
148
149 We consider a handler valid if the path begins with the
150 handler name. It can optionally be followed by "?*", "/*".
151 """
152
153 pattern = re.compile('%s($|\?|/).*' % handler_name)
154 return pattern.match(self.path)
155
initial.commit94958cf2008-07-26 22:42:52 +0000156 def GetMIMETypeFromName(self, file_name):
157 """Returns the mime type for the specified file_name. So far it only looks
158 at the file extension."""
159
160 (shortname, extension) = os.path.splitext(file_name)
161 if len(extension) == 0:
162 # no extension.
163 return self._default_mime_type
164
ericroman@google.comc17ca532009-05-07 03:51:05 +0000165 # extension starts with a dot, so we need to remove it
166 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000167
168 def KillHandler(self):
169 """This request handler kills the server, for use when we're done"
170 with the a particular test."""
171
172 if (self.path.find("kill") < 0):
173 return False
174
175 self.send_response(200)
176 self.send_header('Content-type', 'text/html')
177 self.send_header('Cache-Control', 'max-age=0')
178 self.end_headers()
179 self.wfile.write("Time to die")
180 self.server.stop = True
181
182 return True
183
184 def NoCacheMaxAgeTimeHandler(self):
185 """This request handler yields a page with the title set to the current
186 system time, and no caching requested."""
187
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000188 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000189 return False
190
191 self.send_response(200)
192 self.send_header('Cache-Control', 'max-age=0')
193 self.send_header('Content-type', 'text/html')
194 self.end_headers()
195
maruel@google.come250a9b2009-03-10 17:39:46 +0000196 self.wfile.write('<html><head><title>%s</title></head></html>' %
197 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000198
199 return True
200
201 def NoCacheTimeHandler(self):
202 """This request handler yields a page with the title set to the current
203 system time, and no caching requested."""
204
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000205 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000206 return False
207
208 self.send_response(200)
209 self.send_header('Cache-Control', 'no-cache')
210 self.send_header('Content-type', 'text/html')
211 self.end_headers()
212
maruel@google.come250a9b2009-03-10 17:39:46 +0000213 self.wfile.write('<html><head><title>%s</title></head></html>' %
214 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000215
216 return True
217
218 def CacheTimeHandler(self):
219 """This request handler yields a page with the title set to the current
220 system time, and allows caching for one minute."""
221
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000222 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000223 return False
224
225 self.send_response(200)
226 self.send_header('Cache-Control', 'max-age=60')
227 self.send_header('Content-type', 'text/html')
228 self.end_headers()
229
maruel@google.come250a9b2009-03-10 17:39:46 +0000230 self.wfile.write('<html><head><title>%s</title></head></html>' %
231 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000232
233 return True
234
235 def CacheExpiresHandler(self):
236 """This request handler yields a page with the title set to the current
237 system time, and set the page to expire on 1 Jan 2099."""
238
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000239 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000240 return False
241
242 self.send_response(200)
243 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
244 self.send_header('Content-type', 'text/html')
245 self.end_headers()
246
maruel@google.come250a9b2009-03-10 17:39:46 +0000247 self.wfile.write('<html><head><title>%s</title></head></html>' %
248 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000249
250 return True
251
252 def CacheProxyRevalidateHandler(self):
253 """This request handler yields a page with the title set to the current
254 system time, and allows caching for 60 seconds"""
255
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000256 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000257 return False
258
259 self.send_response(200)
260 self.send_header('Content-type', 'text/html')
261 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
262 self.end_headers()
263
maruel@google.come250a9b2009-03-10 17:39:46 +0000264 self.wfile.write('<html><head><title>%s</title></head></html>' %
265 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000266
267 return True
268
269 def CachePrivateHandler(self):
270 """This request handler yields a page with the title set to the current
271 system time, and allows caching for 5 seconds."""
272
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000273 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000274 return False
275
276 self.send_response(200)
277 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000278 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000279 self.end_headers()
280
maruel@google.come250a9b2009-03-10 17:39:46 +0000281 self.wfile.write('<html><head><title>%s</title></head></html>' %
282 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000283
284 return True
285
286 def CachePublicHandler(self):
287 """This request handler yields a page with the title set to the current
288 system time, and allows caching for 5 seconds."""
289
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000290 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000291 return False
292
293 self.send_response(200)
294 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000295 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000296 self.end_headers()
297
maruel@google.come250a9b2009-03-10 17:39:46 +0000298 self.wfile.write('<html><head><title>%s</title></head></html>' %
299 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000300
301 return True
302
303 def CacheSMaxAgeHandler(self):
304 """This request handler yields a page with the title set to the current
305 system time, and does not allow for caching."""
306
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000307 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000308 return False
309
310 self.send_response(200)
311 self.send_header('Content-type', 'text/html')
312 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
313 self.end_headers()
314
maruel@google.come250a9b2009-03-10 17:39:46 +0000315 self.wfile.write('<html><head><title>%s</title></head></html>' %
316 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000317
318 return True
319
320 def CacheMustRevalidateHandler(self):
321 """This request handler yields a page with the title set to the current
322 system time, and does not allow caching."""
323
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000324 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000325 return False
326
327 self.send_response(200)
328 self.send_header('Content-type', 'text/html')
329 self.send_header('Cache-Control', 'must-revalidate')
330 self.end_headers()
331
maruel@google.come250a9b2009-03-10 17:39:46 +0000332 self.wfile.write('<html><head><title>%s</title></head></html>' %
333 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000334
335 return True
336
337 def CacheMustRevalidateMaxAgeHandler(self):
338 """This request handler yields a page with the title set to the current
339 system time, and does not allow caching event though max-age of 60
340 seconds is specified."""
341
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000342 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000343 return False
344
345 self.send_response(200)
346 self.send_header('Content-type', 'text/html')
347 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
348 self.end_headers()
349
maruel@google.come250a9b2009-03-10 17:39:46 +0000350 self.wfile.write('<html><head><title>%s</title></head></html>' %
351 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000352
353 return True
354
initial.commit94958cf2008-07-26 22:42:52 +0000355 def CacheNoStoreHandler(self):
356 """This request handler yields a page with the title set to the current
357 system time, and does not allow the page to be stored."""
358
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000359 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000360 return False
361
362 self.send_response(200)
363 self.send_header('Content-type', 'text/html')
364 self.send_header('Cache-Control', 'no-store')
365 self.end_headers()
366
maruel@google.come250a9b2009-03-10 17:39:46 +0000367 self.wfile.write('<html><head><title>%s</title></head></html>' %
368 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000369
370 return True
371
372 def CacheNoStoreMaxAgeHandler(self):
373 """This request handler yields a page with the title set to the current
374 system time, and does not allow the page to be stored even though max-age
375 of 60 seconds is specified."""
376
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000377 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000378 return False
379
380 self.send_response(200)
381 self.send_header('Content-type', 'text/html')
382 self.send_header('Cache-Control', 'max-age=60, no-store')
383 self.end_headers()
384
maruel@google.come250a9b2009-03-10 17:39:46 +0000385 self.wfile.write('<html><head><title>%s</title></head></html>' %
386 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000387
388 return True
389
390
391 def CacheNoTransformHandler(self):
392 """This request handler yields a page with the title set to the current
393 system time, and does not allow the content to transformed during
394 user-agent caching"""
395
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000396 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000397 return False
398
399 self.send_response(200)
400 self.send_header('Content-type', 'text/html')
401 self.send_header('Cache-Control', 'no-transform')
402 self.end_headers()
403
maruel@google.come250a9b2009-03-10 17:39:46 +0000404 self.wfile.write('<html><head><title>%s</title></head></html>' %
405 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000406
407 return True
408
409 def EchoHeader(self):
410 """This handler echoes back the value of a specific request header."""
411
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000412 if not self._ShouldHandleRequest("/echoheader"):
initial.commit94958cf2008-07-26 22:42:52 +0000413 return False
414
415 query_char = self.path.find('?')
416 if query_char != -1:
417 header_name = self.path[query_char+1:]
418
419 self.send_response(200)
420 self.send_header('Content-type', 'text/plain')
421 self.send_header('Cache-control', 'max-age=60000')
422 # insert a vary header to properly indicate that the cachability of this
423 # request is subject to value of the request header being echoed.
424 if len(header_name) > 0:
425 self.send_header('Vary', header_name)
426 self.end_headers()
427
428 if len(header_name) > 0:
429 self.wfile.write(self.headers.getheader(header_name))
430
431 return True
432
433 def EchoHandler(self):
434 """This handler just echoes back the payload of the request, for testing
435 form submission."""
436
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000437 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000438 return False
439
440 self.send_response(200)
441 self.send_header('Content-type', 'text/html')
442 self.end_headers()
443 length = int(self.headers.getheader('content-length'))
444 request = self.rfile.read(length)
445 self.wfile.write(request)
446 return True
447
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000448 def WriteFile(self):
449 """This is handler dumps the content of POST request to a disk file into
450 the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000451
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000452 prefix='/writefile/'
453 if not self.path.startswith(prefix):
454 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000455
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000456 file_name = self.path[len(prefix):]
457
458 # do not allow fancy chars in file name
459 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
460 if len(file_name) and file_name[0] != '.':
461 path = os.path.join(self.server.data_dir, 'dump', file_name);
462 length = int(self.headers.getheader('content-length'))
463 request = self.rfile.read(length)
464 f = open(path, "wb")
465 f.write(request);
466 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000467
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000468 self.send_response(200)
469 self.send_header('Content-type', 'text/html')
470 self.end_headers()
471 self.wfile.write('<html>%s</html>' % file_name)
472 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000473
initial.commit94958cf2008-07-26 22:42:52 +0000474 def EchoTitleHandler(self):
475 """This handler is like Echo, but sets the page title to the request."""
476
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000477 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000478 return False
479
480 self.send_response(200)
481 self.send_header('Content-type', 'text/html')
482 self.end_headers()
483 length = int(self.headers.getheader('content-length'))
484 request = self.rfile.read(length)
485 self.wfile.write('<html><head><title>')
486 self.wfile.write(request)
487 self.wfile.write('</title></head></html>')
488 return True
489
490 def EchoAllHandler(self):
491 """This handler yields a (more) human-readable page listing information
492 about the request header & contents."""
493
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000494 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000495 return False
496
497 self.send_response(200)
498 self.send_header('Content-type', 'text/html')
499 self.end_headers()
500 self.wfile.write('<html><head><style>'
501 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
502 '</style></head><body>'
503 '<div style="float: right">'
504 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
505 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000506
ericroman@google.coma47622b2008-11-15 04:36:51 +0000507 if self.command == 'POST':
508 length = int(self.headers.getheader('content-length'))
509 qs = self.rfile.read(length)
510 params = cgi.parse_qs(qs, keep_blank_values=1)
511
512 for param in params:
513 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000514
515 self.wfile.write('</pre>')
516
517 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
518
519 self.wfile.write('</body></html>')
520 return True
521
522 def DownloadHandler(self):
523 """This handler sends a downloadable file with or without reporting
524 the size (6K)."""
525
526 if self.path.startswith("/download-unknown-size"):
527 send_length = False
528 elif self.path.startswith("/download-known-size"):
529 send_length = True
530 else:
531 return False
532
533 #
534 # The test which uses this functionality is attempting to send
535 # small chunks of data to the client. Use a fairly large buffer
536 # so that we'll fill chrome's IO buffer enough to force it to
537 # actually write the data.
538 # See also the comments in the client-side of this test in
539 # download_uitest.cc
540 #
541 size_chunk1 = 35*1024
542 size_chunk2 = 10*1024
543
544 self.send_response(200)
545 self.send_header('Content-type', 'application/octet-stream')
546 self.send_header('Cache-Control', 'max-age=0')
547 if send_length:
548 self.send_header('Content-Length', size_chunk1 + size_chunk2)
549 self.end_headers()
550
551 # First chunk of data:
552 self.wfile.write("*" * size_chunk1)
553 self.wfile.flush()
554
555 # handle requests until one of them clears this flag.
556 self.server.waitForDownload = True
557 while self.server.waitForDownload:
558 self.server.handle_request()
559
560 # Second chunk of data:
561 self.wfile.write("*" * size_chunk2)
562 return True
563
564 def DownloadFinishHandler(self):
565 """This handler just tells the server to finish the current download."""
566
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000567 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000568 return False
569
570 self.server.waitForDownload = False
571 self.send_response(200)
572 self.send_header('Content-type', 'text/html')
573 self.send_header('Cache-Control', 'max-age=0')
574 self.end_headers()
575 return True
576
577 def FileHandler(self):
578 """This handler sends the contents of the requested file. Wow, it's like
579 a real webserver!"""
580
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000581 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000582 if not self.path.startswith(prefix):
583 return False
584
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000585 # Consume a request body if present.
586 if self.command == 'POST':
587 self.rfile.read(int(self.headers.getheader('content-length')))
588
initial.commit94958cf2008-07-26 22:42:52 +0000589 file = self.path[len(prefix):]
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000590 if file.find('?') > -1:
591 # Ignore the query parameters entirely.
592 url, querystring = file.split('?')
593 else:
594 url = file
595 entries = url.split('/')
initial.commit94958cf2008-07-26 22:42:52 +0000596 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000597 if os.path.isdir(path):
598 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000599
600 if not os.path.isfile(path):
601 print "File not found " + file + " full path:" + path
602 self.send_error(404)
603 return True
604
605 f = open(path, "rb")
606 data = f.read()
607 f.close()
608
609 # If file.mock-http-headers exists, it contains the headers we
610 # should send. Read them in and parse them.
611 headers_path = path + '.mock-http-headers'
612 if os.path.isfile(headers_path):
613 f = open(headers_path, "r")
614
615 # "HTTP/1.1 200 OK"
616 response = f.readline()
617 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
618 self.send_response(int(status_code))
619
620 for line in f:
621 # "name: value"
622 name, value = re.findall('(\S+):\s*(.*)', line)[0]
623 self.send_header(name, value)
624 f.close()
625 else:
626 # Could be more generic once we support mime-type sniffing, but for
627 # now we need to set it explicitly.
628 self.send_response(200)
629 self.send_header('Content-type', self.GetMIMETypeFromName(file))
630 self.send_header('Content-Length', len(data))
631 self.end_headers()
632
633 self.wfile.write(data)
634
635 return True
636
637 def RealFileWithCommonHeaderHandler(self):
638 """This handler sends the contents of the requested file without the pseudo
639 http head!"""
640
641 prefix='/realfiles/'
642 if not self.path.startswith(prefix):
643 return False
644
645 file = self.path[len(prefix):]
646 path = os.path.join(self.server.data_dir, file)
647
648 try:
649 f = open(path, "rb")
650 data = f.read()
651 f.close()
652
653 # just simply set the MIME as octal stream
654 self.send_response(200)
655 self.send_header('Content-type', 'application/octet-stream')
656 self.end_headers()
657
658 self.wfile.write(data)
659 except:
660 self.send_error(404)
661
662 return True
663
664 def RealBZ2FileWithCommonHeaderHandler(self):
665 """This handler sends the bzip2 contents of the requested file with
666 corresponding Content-Encoding field in http head!"""
667
668 prefix='/realbz2files/'
669 if not self.path.startswith(prefix):
670 return False
671
672 parts = self.path.split('?')
673 file = parts[0][len(prefix):]
674 path = os.path.join(self.server.data_dir, file) + '.bz2'
675
676 if len(parts) > 1:
677 options = parts[1]
678 else:
679 options = ''
680
681 try:
682 self.send_response(200)
683 accept_encoding = self.headers.get("Accept-Encoding")
684 if accept_encoding.find("bzip2") != -1:
685 f = open(path, "rb")
686 data = f.read()
687 f.close()
688 self.send_header('Content-Encoding', 'bzip2')
689 self.send_header('Content-type', 'application/x-bzip2')
690 self.end_headers()
691 if options == 'incremental-header':
692 self.wfile.write(data[:1])
693 self.wfile.flush()
694 time.sleep(1.0)
695 self.wfile.write(data[1:])
696 else:
697 self.wfile.write(data)
698 else:
699 """client do not support bzip2 format, send pseudo content
700 """
701 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
702 self.end_headers()
703 self.wfile.write("you do not support bzip2 encoding")
704 except:
705 self.send_error(404)
706
707 return True
708
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000709 def SetCookieHandler(self):
710 """This handler just sets a cookie, for testing cookie handling."""
711
712 if not self._ShouldHandleRequest("/set-cookie"):
713 return False
714
715 query_char = self.path.find('?')
716 if query_char != -1:
717 cookie_values = self.path[query_char + 1:].split('&')
718 else:
719 cookie_values = ("",)
720 self.send_response(200)
721 self.send_header('Content-type', 'text/html')
722 for cookie_value in cookie_values:
723 self.send_header('Set-Cookie', '%s' % cookie_value)
724 self.end_headers()
725 for cookie_value in cookie_values:
726 self.wfile.write('%s' % cookie_value)
727 return True
728
initial.commit94958cf2008-07-26 22:42:52 +0000729 def AuthBasicHandler(self):
730 """This handler tests 'Basic' authentication. It just sends a page with
731 title 'user/pass' if you succeed."""
732
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000733 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000734 return False
735
736 username = userpass = password = b64str = ""
737
ericroman@google.com239b4d82009-03-27 04:00:22 +0000738 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
739
initial.commit94958cf2008-07-26 22:42:52 +0000740 auth = self.headers.getheader('authorization')
741 try:
742 if not auth:
743 raise Exception('no auth')
744 b64str = re.findall(r'Basic (\S+)', auth)[0]
745 userpass = base64.b64decode(b64str)
746 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
747 if password != 'secret':
748 raise Exception('wrong password')
749 except Exception, e:
750 # Authentication failed.
751 self.send_response(401)
752 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
753 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000754 if set_cookie_if_challenged:
755 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000756 self.end_headers()
757 self.wfile.write('<html><head>')
758 self.wfile.write('<title>Denied: %s</title>' % e)
759 self.wfile.write('</head><body>')
760 self.wfile.write('auth=%s<p>' % auth)
761 self.wfile.write('b64str=%s<p>' % b64str)
762 self.wfile.write('username: %s<p>' % username)
763 self.wfile.write('userpass: %s<p>' % userpass)
764 self.wfile.write('password: %s<p>' % password)
765 self.wfile.write('You sent:<br>%s<p>' % self.headers)
766 self.wfile.write('</body></html>')
767 return True
768
769 # Authentication successful. (Return a cachable response to allow for
770 # testing cached pages that require authentication.)
771 if_none_match = self.headers.getheader('if-none-match')
772 if if_none_match == "abc":
773 self.send_response(304)
774 self.end_headers()
775 else:
776 self.send_response(200)
777 self.send_header('Content-type', 'text/html')
778 self.send_header('Cache-control', 'max-age=60000')
779 self.send_header('Etag', 'abc')
780 self.end_headers()
781 self.wfile.write('<html><head>')
782 self.wfile.write('<title>%s/%s</title>' % (username, password))
783 self.wfile.write('</head><body>')
784 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000785 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000786 self.wfile.write('</body></html>')
787
788 return True
789
790 def AuthDigestHandler(self):
791 """This handler tests 'Digest' authentication. It just sends a page with
792 title 'user/pass' if you succeed."""
793
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000794 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000795 return False
796
797 # Periodically generate a new nonce. Technically we should incorporate
798 # the request URL into this, but we don't care for testing.
799 nonce_life = 10
800 stale = False
maruel@google.come250a9b2009-03-10 17:39:46 +0000801 if (not self.server.nonce or
802 (time.time() - self.server.nonce_time > nonce_life)):
initial.commit94958cf2008-07-26 22:42:52 +0000803 if self.server.nonce:
804 stale = True
805 self.server.nonce_time = time.time()
806 self.server.nonce = \
maruel@google.come250a9b2009-03-10 17:39:46 +0000807 _new_md5(time.ctime(self.server.nonce_time) +
808 'privatekey').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000809
810 nonce = self.server.nonce
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000811 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000812 password = 'secret'
813 realm = 'testrealm'
814
815 auth = self.headers.getheader('authorization')
816 pairs = {}
817 try:
818 if not auth:
819 raise Exception('no auth')
820 if not auth.startswith('Digest'):
821 raise Exception('not digest')
822 # Pull out all the name="value" pairs as a dictionary.
823 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
824
825 # Make sure it's all valid.
826 if pairs['nonce'] != nonce:
827 raise Exception('wrong nonce')
828 if pairs['opaque'] != opaque:
829 raise Exception('wrong opaque')
830
831 # Check the 'response' value and make sure it matches our magic hash.
832 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000833 hash_a1 = _new_md5(
834 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000835 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000836 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000837 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000838 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
839 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000840 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000841
842 if pairs['response'] != response:
843 raise Exception('wrong password')
844 except Exception, e:
845 # Authentication failed.
846 self.send_response(401)
847 hdr = ('Digest '
848 'realm="%s", '
849 'domain="/", '
850 'qop="auth", '
851 'algorithm=MD5, '
852 'nonce="%s", '
853 'opaque="%s"') % (realm, nonce, opaque)
854 if stale:
855 hdr += ', stale="TRUE"'
856 self.send_header('WWW-Authenticate', hdr)
857 self.send_header('Content-type', 'text/html')
858 self.end_headers()
859 self.wfile.write('<html><head>')
860 self.wfile.write('<title>Denied: %s</title>' % e)
861 self.wfile.write('</head><body>')
862 self.wfile.write('auth=%s<p>' % auth)
863 self.wfile.write('pairs=%s<p>' % pairs)
864 self.wfile.write('You sent:<br>%s<p>' % self.headers)
865 self.wfile.write('We are replying:<br>%s<p>' % hdr)
866 self.wfile.write('</body></html>')
867 return True
868
869 # Authentication successful.
870 self.send_response(200)
871 self.send_header('Content-type', 'text/html')
872 self.end_headers()
873 self.wfile.write('<html><head>')
874 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
875 self.wfile.write('</head><body>')
876 self.wfile.write('auth=%s<p>' % auth)
877 self.wfile.write('pairs=%s<p>' % pairs)
878 self.wfile.write('</body></html>')
879
880 return True
881
882 def SlowServerHandler(self):
883 """Wait for the user suggested time before responding. The syntax is
884 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000885 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000886 return False
887 query_char = self.path.find('?')
888 wait_sec = 1.0
889 if query_char >= 0:
890 try:
891 wait_sec = int(self.path[query_char + 1:])
892 except ValueError:
893 pass
894 time.sleep(wait_sec)
895 self.send_response(200)
896 self.send_header('Content-type', 'text/plain')
897 self.end_headers()
898 self.wfile.write("waited %d seconds" % wait_sec)
899 return True
900
901 def ContentTypeHandler(self):
902 """Returns a string of html with the given content type. E.g.,
903 /contenttype?text/css returns an html file with the Content-Type
904 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000905 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000906 return False
907 query_char = self.path.find('?')
908 content_type = self.path[query_char + 1:].strip()
909 if not content_type:
910 content_type = 'text/html'
911 self.send_response(200)
912 self.send_header('Content-Type', content_type)
913 self.end_headers()
914 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
915 return True
916
917 def ServerRedirectHandler(self):
918 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000919 '/server-redirect?http://foo.bar/asdf' to redirect to
920 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000921
922 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000923 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000924 return False
925
926 query_char = self.path.find('?')
927 if query_char < 0 or len(self.path) <= query_char + 1:
928 self.sendRedirectHelp(test_name)
929 return True
930 dest = self.path[query_char + 1:]
931
932 self.send_response(301) # moved permanently
933 self.send_header('Location', dest)
934 self.send_header('Content-type', 'text/html')
935 self.end_headers()
936 self.wfile.write('<html><head>')
937 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
938
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000939 return True
initial.commit94958cf2008-07-26 22:42:52 +0000940
941 def ClientRedirectHandler(self):
942 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000943 '/client-redirect?http://foo.bar/asdf' to redirect to
944 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000945
946 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000947 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000948 return False
949
950 query_char = self.path.find('?');
951 if query_char < 0 or len(self.path) <= query_char + 1:
952 self.sendRedirectHelp(test_name)
953 return True
954 dest = self.path[query_char + 1:]
955
956 self.send_response(200)
957 self.send_header('Content-type', 'text/html')
958 self.end_headers()
959 self.wfile.write('<html><head>')
960 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
961 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
962
963 return True
964
965 def DefaultResponseHandler(self):
966 """This is the catch-all response handler for requests that aren't handled
967 by one of the special handlers above.
968 Note that we specify the content-length as without it the https connection
969 is not closed properly (and the browser keeps expecting data)."""
970
971 contents = "Default response given for path: " + self.path
972 self.send_response(200)
973 self.send_header('Content-type', 'text/html')
974 self.send_header("Content-Length", len(contents))
975 self.end_headers()
976 self.wfile.write(contents)
977 return True
978
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000979 def RedirectConnectHandler(self):
980 """Sends a redirect to the CONNECT request for www.redirect.com. This
981 response is not specified by the RFC, so the browser should not follow
982 the redirect."""
983
984 if (self.path.find("www.redirect.com") < 0):
985 return False
986
987 dest = "http://www.destination.com/foo.js"
988
989 self.send_response(302) # moved temporarily
990 self.send_header('Location', dest)
991 self.send_header('Connection', 'close')
992 self.end_headers()
993 return True
994
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000995 def ServerAuthConnectHandler(self):
996 """Sends a 401 to the CONNECT request for www.server-auth.com. This
997 response doesn't make sense because the proxy server cannot request
998 server authentication."""
999
1000 if (self.path.find("www.server-auth.com") < 0):
1001 return False
1002
1003 challenge = 'Basic realm="WallyWorld"'
1004
1005 self.send_response(401) # unauthorized
1006 self.send_header('WWW-Authenticate', challenge)
1007 self.send_header('Connection', 'close')
1008 self.end_headers()
1009 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001010
1011 def DefaultConnectResponseHandler(self):
1012 """This is the catch-all response handler for CONNECT requests that aren't
1013 handled by one of the special handlers above. Real Web servers respond
1014 with 400 to CONNECT requests."""
1015
1016 contents = "Your client has issued a malformed or illegal request."
1017 self.send_response(400) # bad request
1018 self.send_header('Content-type', 'text/html')
1019 self.send_header("Content-Length", len(contents))
1020 self.end_headers()
1021 self.wfile.write(contents)
1022 return True
1023
1024 def do_CONNECT(self):
1025 for handler in self._connect_handlers:
1026 if handler():
1027 return
1028
initial.commit94958cf2008-07-26 22:42:52 +00001029 def do_GET(self):
1030 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001031 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001032 return
1033
1034 def do_POST(self):
1035 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001036 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001037 return
1038
1039 # called by the redirect handling function when there is no parameter
1040 def sendRedirectHelp(self, redirect_name):
1041 self.send_response(200)
1042 self.send_header('Content-type', 'text/html')
1043 self.end_headers()
1044 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1045 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1046 self.wfile.write('</body></html>')
1047
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001048def MakeDumpDir(data_dir):
1049 """Create directory named 'dump' where uploaded data via HTTP POST request
1050 will be stored. If the directory already exists all files and subdirectories
1051 will be deleted."""
1052 dump_dir = os.path.join(data_dir, 'dump');
1053 if os.path.isdir(dump_dir):
1054 shutil.rmtree(dump_dir)
1055 os.mkdir(dump_dir)
1056
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001057def MakeDataDir():
1058 if options.data_dir:
1059 if not os.path.isdir(options.data_dir):
1060 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1061 return None
1062 my_data_dir = options.data_dir
1063 else:
1064 # Create the default path to our data dir, relative to the exe dir.
1065 my_data_dir = os.path.dirname(sys.argv[0])
1066 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1067 "test", "data")
1068
1069 #TODO(ibrar): Must use Find* funtion defined in google\tools
1070 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1071
1072 return my_data_dir
1073
initial.commit94958cf2008-07-26 22:42:52 +00001074def main(options, args):
1075 # redirect output to a log file so it doesn't spam the unit test output
1076 logfile = open('testserver.log', 'w')
1077 sys.stderr = sys.stdout = logfile
1078
1079 port = options.port
1080
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001081 if options.server_type == SERVER_HTTP:
1082 if options.cert:
1083 # let's make sure the cert file exists.
1084 if not os.path.isfile(options.cert):
1085 print 'specified cert file not found: ' + options.cert + ' exiting...'
1086 return
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001087 if options.forking:
1088 server_class = ForkingHTTPSServer
1089 else:
1090 server_class = HTTPSServer
1091 server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001092 print 'HTTPS server started on port %d...' % port
1093 else:
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001094 if options.forking:
1095 server_class = ForkingHTTPServer
1096 else:
1097 server_class = StoppableHTTPServer
1098 server = server_class(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001099 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001100
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001101 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001102 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001103 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001104
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001105 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001106 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001107 my_data_dir = MakeDataDir()
1108
1109 def line_logger(msg):
1110 if (msg.find("kill") >= 0):
1111 server.stop = True
1112 print 'shutting down server'
1113 sys.exit(0)
1114
1115 # Instantiate a dummy authorizer for managing 'virtual' users
1116 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1117
1118 # Define a new user having full r/w permissions and a read-only
1119 # anonymous user
1120 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1121
1122 authorizer.add_anonymous(my_data_dir)
1123
1124 # Instantiate FTP handler class
1125 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1126 ftp_handler.authorizer = authorizer
1127 pyftpdlib.ftpserver.logline = line_logger
1128
1129 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001130 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1131 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001132
1133 # Instantiate FTP server class and listen to 127.0.0.1:port
1134 address = ('127.0.0.1', port)
1135 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1136 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001137
1138 try:
1139 server.serve_forever()
1140 except KeyboardInterrupt:
1141 print 'shutting down server'
1142 server.stop = True
1143
1144if __name__ == '__main__':
1145 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001146 option_parser.add_option("-f", '--ftp', action='store_const',
1147 const=SERVER_FTP, default=SERVER_HTTP,
1148 dest='server_type',
1149 help='FTP or HTTP server default HTTP')
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001150 option_parser.add_option('--forking', action='store_true', default=False,
1151 dest='forking',
1152 help='Serve each request in a separate process')
initial.commit94958cf2008-07-26 22:42:52 +00001153 option_parser.add_option('', '--port', default='8888', type='int',
1154 help='Port used by the server')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001155 option_parser.add_option('', '--data-dir', dest='data_dir',
initial.commit94958cf2008-07-26 22:42:52 +00001156 help='Directory from which to read the files')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001157 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001158 help='Specify that https should be used, specify '
1159 'the path to the cert containing the private key '
1160 'the server should use')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001161 option_parser.add_option('', '--file-root-url', default='/files/',
1162 help='Specify a root URL for files served.')
initial.commit94958cf2008-07-26 22:42:52 +00001163 options, args = option_parser.parse_args()
1164
1165 sys.exit(main(options, args))