blob: 065ea7baf44df79a7e1aa8547343abe9083badc3 [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',
137 'jpg' : 'image/jpeg'
138 }
initial.commit94958cf2008-07-26 22:42:52 +0000139 self._default_mime_type = 'text/html'
140
maruel@google.come250a9b2009-03-10 17:39:46 +0000141 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
142 client_address,
143 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000144
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000145 def _ShouldHandleRequest(self, handler_name):
146 """Determines if the path can be handled by the handler.
147
148 We consider a handler valid if the path begins with the
149 handler name. It can optionally be followed by "?*", "/*".
150 """
151
152 pattern = re.compile('%s($|\?|/).*' % handler_name)
153 return pattern.match(self.path)
154
initial.commit94958cf2008-07-26 22:42:52 +0000155 def GetMIMETypeFromName(self, file_name):
156 """Returns the mime type for the specified file_name. So far it only looks
157 at the file extension."""
158
159 (shortname, extension) = os.path.splitext(file_name)
160 if len(extension) == 0:
161 # no extension.
162 return self._default_mime_type
163
ericroman@google.comc17ca532009-05-07 03:51:05 +0000164 # extension starts with a dot, so we need to remove it
165 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000166
167 def KillHandler(self):
168 """This request handler kills the server, for use when we're done"
169 with the a particular test."""
170
171 if (self.path.find("kill") < 0):
172 return False
173
174 self.send_response(200)
175 self.send_header('Content-type', 'text/html')
176 self.send_header('Cache-Control', 'max-age=0')
177 self.end_headers()
178 self.wfile.write("Time to die")
179 self.server.stop = True
180
181 return True
182
183 def NoCacheMaxAgeTimeHandler(self):
184 """This request handler yields a page with the title set to the current
185 system time, and no caching requested."""
186
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000187 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000188 return False
189
190 self.send_response(200)
191 self.send_header('Cache-Control', 'max-age=0')
192 self.send_header('Content-type', 'text/html')
193 self.end_headers()
194
maruel@google.come250a9b2009-03-10 17:39:46 +0000195 self.wfile.write('<html><head><title>%s</title></head></html>' %
196 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000197
198 return True
199
200 def NoCacheTimeHandler(self):
201 """This request handler yields a page with the title set to the current
202 system time, and no caching requested."""
203
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000204 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000205 return False
206
207 self.send_response(200)
208 self.send_header('Cache-Control', 'no-cache')
209 self.send_header('Content-type', 'text/html')
210 self.end_headers()
211
maruel@google.come250a9b2009-03-10 17:39:46 +0000212 self.wfile.write('<html><head><title>%s</title></head></html>' %
213 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000214
215 return True
216
217 def CacheTimeHandler(self):
218 """This request handler yields a page with the title set to the current
219 system time, and allows caching for one minute."""
220
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000221 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000222 return False
223
224 self.send_response(200)
225 self.send_header('Cache-Control', 'max-age=60')
226 self.send_header('Content-type', 'text/html')
227 self.end_headers()
228
maruel@google.come250a9b2009-03-10 17:39:46 +0000229 self.wfile.write('<html><head><title>%s</title></head></html>' %
230 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000231
232 return True
233
234 def CacheExpiresHandler(self):
235 """This request handler yields a page with the title set to the current
236 system time, and set the page to expire on 1 Jan 2099."""
237
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000238 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000239 return False
240
241 self.send_response(200)
242 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
243 self.send_header('Content-type', 'text/html')
244 self.end_headers()
245
maruel@google.come250a9b2009-03-10 17:39:46 +0000246 self.wfile.write('<html><head><title>%s</title></head></html>' %
247 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000248
249 return True
250
251 def CacheProxyRevalidateHandler(self):
252 """This request handler yields a page with the title set to the current
253 system time, and allows caching for 60 seconds"""
254
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000255 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000256 return False
257
258 self.send_response(200)
259 self.send_header('Content-type', 'text/html')
260 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
261 self.end_headers()
262
maruel@google.come250a9b2009-03-10 17:39:46 +0000263 self.wfile.write('<html><head><title>%s</title></head></html>' %
264 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000265
266 return True
267
268 def CachePrivateHandler(self):
269 """This request handler yields a page with the title set to the current
270 system time, and allows caching for 5 seconds."""
271
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000272 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000273 return False
274
275 self.send_response(200)
276 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000277 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000278 self.end_headers()
279
maruel@google.come250a9b2009-03-10 17:39:46 +0000280 self.wfile.write('<html><head><title>%s</title></head></html>' %
281 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000282
283 return True
284
285 def CachePublicHandler(self):
286 """This request handler yields a page with the title set to the current
287 system time, and allows caching for 5 seconds."""
288
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000289 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000290 return False
291
292 self.send_response(200)
293 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000294 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000295 self.end_headers()
296
maruel@google.come250a9b2009-03-10 17:39:46 +0000297 self.wfile.write('<html><head><title>%s</title></head></html>' %
298 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000299
300 return True
301
302 def CacheSMaxAgeHandler(self):
303 """This request handler yields a page with the title set to the current
304 system time, and does not allow for caching."""
305
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000306 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000307 return False
308
309 self.send_response(200)
310 self.send_header('Content-type', 'text/html')
311 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
312 self.end_headers()
313
maruel@google.come250a9b2009-03-10 17:39:46 +0000314 self.wfile.write('<html><head><title>%s</title></head></html>' %
315 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000316
317 return True
318
319 def CacheMustRevalidateHandler(self):
320 """This request handler yields a page with the title set to the current
321 system time, and does not allow caching."""
322
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000323 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000324 return False
325
326 self.send_response(200)
327 self.send_header('Content-type', 'text/html')
328 self.send_header('Cache-Control', 'must-revalidate')
329 self.end_headers()
330
maruel@google.come250a9b2009-03-10 17:39:46 +0000331 self.wfile.write('<html><head><title>%s</title></head></html>' %
332 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000333
334 return True
335
336 def CacheMustRevalidateMaxAgeHandler(self):
337 """This request handler yields a page with the title set to the current
338 system time, and does not allow caching event though max-age of 60
339 seconds is specified."""
340
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000341 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000342 return False
343
344 self.send_response(200)
345 self.send_header('Content-type', 'text/html')
346 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
347 self.end_headers()
348
maruel@google.come250a9b2009-03-10 17:39:46 +0000349 self.wfile.write('<html><head><title>%s</title></head></html>' %
350 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000351
352 return True
353
initial.commit94958cf2008-07-26 22:42:52 +0000354 def CacheNoStoreHandler(self):
355 """This request handler yields a page with the title set to the current
356 system time, and does not allow the page to be stored."""
357
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000358 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000359 return False
360
361 self.send_response(200)
362 self.send_header('Content-type', 'text/html')
363 self.send_header('Cache-Control', 'no-store')
364 self.end_headers()
365
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 self.wfile.write('<html><head><title>%s</title></head></html>' %
367 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000368
369 return True
370
371 def CacheNoStoreMaxAgeHandler(self):
372 """This request handler yields a page with the title set to the current
373 system time, and does not allow the page to be stored even though max-age
374 of 60 seconds is specified."""
375
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000376 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000377 return False
378
379 self.send_response(200)
380 self.send_header('Content-type', 'text/html')
381 self.send_header('Cache-Control', 'max-age=60, no-store')
382 self.end_headers()
383
maruel@google.come250a9b2009-03-10 17:39:46 +0000384 self.wfile.write('<html><head><title>%s</title></head></html>' %
385 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000386
387 return True
388
389
390 def CacheNoTransformHandler(self):
391 """This request handler yields a page with the title set to the current
392 system time, and does not allow the content to transformed during
393 user-agent caching"""
394
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000395 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000396 return False
397
398 self.send_response(200)
399 self.send_header('Content-type', 'text/html')
400 self.send_header('Cache-Control', 'no-transform')
401 self.end_headers()
402
maruel@google.come250a9b2009-03-10 17:39:46 +0000403 self.wfile.write('<html><head><title>%s</title></head></html>' %
404 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000405
406 return True
407
408 def EchoHeader(self):
409 """This handler echoes back the value of a specific request header."""
410
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000411 if not self._ShouldHandleRequest("/echoheader"):
initial.commit94958cf2008-07-26 22:42:52 +0000412 return False
413
414 query_char = self.path.find('?')
415 if query_char != -1:
416 header_name = self.path[query_char+1:]
417
418 self.send_response(200)
419 self.send_header('Content-type', 'text/plain')
420 self.send_header('Cache-control', 'max-age=60000')
421 # insert a vary header to properly indicate that the cachability of this
422 # request is subject to value of the request header being echoed.
423 if len(header_name) > 0:
424 self.send_header('Vary', header_name)
425 self.end_headers()
426
427 if len(header_name) > 0:
428 self.wfile.write(self.headers.getheader(header_name))
429
430 return True
431
432 def EchoHandler(self):
433 """This handler just echoes back the payload of the request, for testing
434 form submission."""
435
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000436 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000437 return False
438
439 self.send_response(200)
440 self.send_header('Content-type', 'text/html')
441 self.end_headers()
442 length = int(self.headers.getheader('content-length'))
443 request = self.rfile.read(length)
444 self.wfile.write(request)
445 return True
446
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000447 def WriteFile(self):
448 """This is handler dumps the content of POST request to a disk file into
449 the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000450
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000451 prefix='/writefile/'
452 if not self.path.startswith(prefix):
453 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000454
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000455 file_name = self.path[len(prefix):]
456
457 # do not allow fancy chars in file name
458 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
459 if len(file_name) and file_name[0] != '.':
460 path = os.path.join(self.server.data_dir, 'dump', file_name);
461 length = int(self.headers.getheader('content-length'))
462 request = self.rfile.read(length)
463 f = open(path, "wb")
464 f.write(request);
465 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000466
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000467 self.send_response(200)
468 self.send_header('Content-type', 'text/html')
469 self.end_headers()
470 self.wfile.write('<html>%s</html>' % file_name)
471 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000472
initial.commit94958cf2008-07-26 22:42:52 +0000473 def EchoTitleHandler(self):
474 """This handler is like Echo, but sets the page title to the request."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
480 self.send_header('Content-type', 'text/html')
481 self.end_headers()
482 length = int(self.headers.getheader('content-length'))
483 request = self.rfile.read(length)
484 self.wfile.write('<html><head><title>')
485 self.wfile.write(request)
486 self.wfile.write('</title></head></html>')
487 return True
488
489 def EchoAllHandler(self):
490 """This handler yields a (more) human-readable page listing information
491 about the request header & contents."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/echoall"):
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 self.wfile.write('<html><head><style>'
500 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
501 '</style></head><body>'
502 '<div style="float: right">'
503 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
504 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000505
ericroman@google.coma47622b2008-11-15 04:36:51 +0000506 if self.command == 'POST':
507 length = int(self.headers.getheader('content-length'))
508 qs = self.rfile.read(length)
509 params = cgi.parse_qs(qs, keep_blank_values=1)
510
511 for param in params:
512 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 self.wfile.write('</pre>')
515
516 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
517
518 self.wfile.write('</body></html>')
519 return True
520
521 def DownloadHandler(self):
522 """This handler sends a downloadable file with or without reporting
523 the size (6K)."""
524
525 if self.path.startswith("/download-unknown-size"):
526 send_length = False
527 elif self.path.startswith("/download-known-size"):
528 send_length = True
529 else:
530 return False
531
532 #
533 # The test which uses this functionality is attempting to send
534 # small chunks of data to the client. Use a fairly large buffer
535 # so that we'll fill chrome's IO buffer enough to force it to
536 # actually write the data.
537 # See also the comments in the client-side of this test in
538 # download_uitest.cc
539 #
540 size_chunk1 = 35*1024
541 size_chunk2 = 10*1024
542
543 self.send_response(200)
544 self.send_header('Content-type', 'application/octet-stream')
545 self.send_header('Cache-Control', 'max-age=0')
546 if send_length:
547 self.send_header('Content-Length', size_chunk1 + size_chunk2)
548 self.end_headers()
549
550 # First chunk of data:
551 self.wfile.write("*" * size_chunk1)
552 self.wfile.flush()
553
554 # handle requests until one of them clears this flag.
555 self.server.waitForDownload = True
556 while self.server.waitForDownload:
557 self.server.handle_request()
558
559 # Second chunk of data:
560 self.wfile.write("*" * size_chunk2)
561 return True
562
563 def DownloadFinishHandler(self):
564 """This handler just tells the server to finish the current download."""
565
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000566 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000567 return False
568
569 self.server.waitForDownload = False
570 self.send_response(200)
571 self.send_header('Content-type', 'text/html')
572 self.send_header('Cache-Control', 'max-age=0')
573 self.end_headers()
574 return True
575
576 def FileHandler(self):
577 """This handler sends the contents of the requested file. Wow, it's like
578 a real webserver!"""
579
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000580 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000581 if not self.path.startswith(prefix):
582 return False
583
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000584 # Consume a request body if present.
585 if self.command == 'POST':
586 self.rfile.read(int(self.headers.getheader('content-length')))
587
initial.commit94958cf2008-07-26 22:42:52 +0000588 file = self.path[len(prefix):]
589 entries = file.split('/');
590 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000591 if os.path.isdir(path):
592 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000593
594 if not os.path.isfile(path):
595 print "File not found " + file + " full path:" + path
596 self.send_error(404)
597 return True
598
599 f = open(path, "rb")
600 data = f.read()
601 f.close()
602
603 # If file.mock-http-headers exists, it contains the headers we
604 # should send. Read them in and parse them.
605 headers_path = path + '.mock-http-headers'
606 if os.path.isfile(headers_path):
607 f = open(headers_path, "r")
608
609 # "HTTP/1.1 200 OK"
610 response = f.readline()
611 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
612 self.send_response(int(status_code))
613
614 for line in f:
615 # "name: value"
616 name, value = re.findall('(\S+):\s*(.*)', line)[0]
617 self.send_header(name, value)
618 f.close()
619 else:
620 # Could be more generic once we support mime-type sniffing, but for
621 # now we need to set it explicitly.
622 self.send_response(200)
623 self.send_header('Content-type', self.GetMIMETypeFromName(file))
624 self.send_header('Content-Length', len(data))
625 self.end_headers()
626
627 self.wfile.write(data)
628
629 return True
630
631 def RealFileWithCommonHeaderHandler(self):
632 """This handler sends the contents of the requested file without the pseudo
633 http head!"""
634
635 prefix='/realfiles/'
636 if not self.path.startswith(prefix):
637 return False
638
639 file = self.path[len(prefix):]
640 path = os.path.join(self.server.data_dir, file)
641
642 try:
643 f = open(path, "rb")
644 data = f.read()
645 f.close()
646
647 # just simply set the MIME as octal stream
648 self.send_response(200)
649 self.send_header('Content-type', 'application/octet-stream')
650 self.end_headers()
651
652 self.wfile.write(data)
653 except:
654 self.send_error(404)
655
656 return True
657
658 def RealBZ2FileWithCommonHeaderHandler(self):
659 """This handler sends the bzip2 contents of the requested file with
660 corresponding Content-Encoding field in http head!"""
661
662 prefix='/realbz2files/'
663 if not self.path.startswith(prefix):
664 return False
665
666 parts = self.path.split('?')
667 file = parts[0][len(prefix):]
668 path = os.path.join(self.server.data_dir, file) + '.bz2'
669
670 if len(parts) > 1:
671 options = parts[1]
672 else:
673 options = ''
674
675 try:
676 self.send_response(200)
677 accept_encoding = self.headers.get("Accept-Encoding")
678 if accept_encoding.find("bzip2") != -1:
679 f = open(path, "rb")
680 data = f.read()
681 f.close()
682 self.send_header('Content-Encoding', 'bzip2')
683 self.send_header('Content-type', 'application/x-bzip2')
684 self.end_headers()
685 if options == 'incremental-header':
686 self.wfile.write(data[:1])
687 self.wfile.flush()
688 time.sleep(1.0)
689 self.wfile.write(data[1:])
690 else:
691 self.wfile.write(data)
692 else:
693 """client do not support bzip2 format, send pseudo content
694 """
695 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
696 self.end_headers()
697 self.wfile.write("you do not support bzip2 encoding")
698 except:
699 self.send_error(404)
700
701 return True
702
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000703 def SetCookieHandler(self):
704 """This handler just sets a cookie, for testing cookie handling."""
705
706 if not self._ShouldHandleRequest("/set-cookie"):
707 return False
708
709 query_char = self.path.find('?')
710 if query_char != -1:
711 cookie_values = self.path[query_char + 1:].split('&')
712 else:
713 cookie_values = ("",)
714 self.send_response(200)
715 self.send_header('Content-type', 'text/html')
716 for cookie_value in cookie_values:
717 self.send_header('Set-Cookie', '%s' % cookie_value)
718 self.end_headers()
719 for cookie_value in cookie_values:
720 self.wfile.write('%s' % cookie_value)
721 return True
722
initial.commit94958cf2008-07-26 22:42:52 +0000723 def AuthBasicHandler(self):
724 """This handler tests 'Basic' authentication. It just sends a page with
725 title 'user/pass' if you succeed."""
726
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000727 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000728 return False
729
730 username = userpass = password = b64str = ""
731
ericroman@google.com239b4d82009-03-27 04:00:22 +0000732 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
733
initial.commit94958cf2008-07-26 22:42:52 +0000734 auth = self.headers.getheader('authorization')
735 try:
736 if not auth:
737 raise Exception('no auth')
738 b64str = re.findall(r'Basic (\S+)', auth)[0]
739 userpass = base64.b64decode(b64str)
740 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
741 if password != 'secret':
742 raise Exception('wrong password')
743 except Exception, e:
744 # Authentication failed.
745 self.send_response(401)
746 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
747 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000748 if set_cookie_if_challenged:
749 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000750 self.end_headers()
751 self.wfile.write('<html><head>')
752 self.wfile.write('<title>Denied: %s</title>' % e)
753 self.wfile.write('</head><body>')
754 self.wfile.write('auth=%s<p>' % auth)
755 self.wfile.write('b64str=%s<p>' % b64str)
756 self.wfile.write('username: %s<p>' % username)
757 self.wfile.write('userpass: %s<p>' % userpass)
758 self.wfile.write('password: %s<p>' % password)
759 self.wfile.write('You sent:<br>%s<p>' % self.headers)
760 self.wfile.write('</body></html>')
761 return True
762
763 # Authentication successful. (Return a cachable response to allow for
764 # testing cached pages that require authentication.)
765 if_none_match = self.headers.getheader('if-none-match')
766 if if_none_match == "abc":
767 self.send_response(304)
768 self.end_headers()
769 else:
770 self.send_response(200)
771 self.send_header('Content-type', 'text/html')
772 self.send_header('Cache-control', 'max-age=60000')
773 self.send_header('Etag', 'abc')
774 self.end_headers()
775 self.wfile.write('<html><head>')
776 self.wfile.write('<title>%s/%s</title>' % (username, password))
777 self.wfile.write('</head><body>')
778 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000779 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000780 self.wfile.write('</body></html>')
781
782 return True
783
784 def AuthDigestHandler(self):
785 """This handler tests 'Digest' authentication. It just sends a page with
786 title 'user/pass' if you succeed."""
787
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000788 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000789 return False
790
791 # Periodically generate a new nonce. Technically we should incorporate
792 # the request URL into this, but we don't care for testing.
793 nonce_life = 10
794 stale = False
maruel@google.come250a9b2009-03-10 17:39:46 +0000795 if (not self.server.nonce or
796 (time.time() - self.server.nonce_time > nonce_life)):
initial.commit94958cf2008-07-26 22:42:52 +0000797 if self.server.nonce:
798 stale = True
799 self.server.nonce_time = time.time()
800 self.server.nonce = \
maruel@google.come250a9b2009-03-10 17:39:46 +0000801 _new_md5(time.ctime(self.server.nonce_time) +
802 'privatekey').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000803
804 nonce = self.server.nonce
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000805 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000806 password = 'secret'
807 realm = 'testrealm'
808
809 auth = self.headers.getheader('authorization')
810 pairs = {}
811 try:
812 if not auth:
813 raise Exception('no auth')
814 if not auth.startswith('Digest'):
815 raise Exception('not digest')
816 # Pull out all the name="value" pairs as a dictionary.
817 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
818
819 # Make sure it's all valid.
820 if pairs['nonce'] != nonce:
821 raise Exception('wrong nonce')
822 if pairs['opaque'] != opaque:
823 raise Exception('wrong opaque')
824
825 # Check the 'response' value and make sure it matches our magic hash.
826 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000827 hash_a1 = _new_md5(
828 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000829 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000830 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000831 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000832 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
833 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000834 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000835
836 if pairs['response'] != response:
837 raise Exception('wrong password')
838 except Exception, e:
839 # Authentication failed.
840 self.send_response(401)
841 hdr = ('Digest '
842 'realm="%s", '
843 'domain="/", '
844 'qop="auth", '
845 'algorithm=MD5, '
846 'nonce="%s", '
847 'opaque="%s"') % (realm, nonce, opaque)
848 if stale:
849 hdr += ', stale="TRUE"'
850 self.send_header('WWW-Authenticate', hdr)
851 self.send_header('Content-type', 'text/html')
852 self.end_headers()
853 self.wfile.write('<html><head>')
854 self.wfile.write('<title>Denied: %s</title>' % e)
855 self.wfile.write('</head><body>')
856 self.wfile.write('auth=%s<p>' % auth)
857 self.wfile.write('pairs=%s<p>' % pairs)
858 self.wfile.write('You sent:<br>%s<p>' % self.headers)
859 self.wfile.write('We are replying:<br>%s<p>' % hdr)
860 self.wfile.write('</body></html>')
861 return True
862
863 # Authentication successful.
864 self.send_response(200)
865 self.send_header('Content-type', 'text/html')
866 self.end_headers()
867 self.wfile.write('<html><head>')
868 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
869 self.wfile.write('</head><body>')
870 self.wfile.write('auth=%s<p>' % auth)
871 self.wfile.write('pairs=%s<p>' % pairs)
872 self.wfile.write('</body></html>')
873
874 return True
875
876 def SlowServerHandler(self):
877 """Wait for the user suggested time before responding. The syntax is
878 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000879 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000880 return False
881 query_char = self.path.find('?')
882 wait_sec = 1.0
883 if query_char >= 0:
884 try:
885 wait_sec = int(self.path[query_char + 1:])
886 except ValueError:
887 pass
888 time.sleep(wait_sec)
889 self.send_response(200)
890 self.send_header('Content-type', 'text/plain')
891 self.end_headers()
892 self.wfile.write("waited %d seconds" % wait_sec)
893 return True
894
895 def ContentTypeHandler(self):
896 """Returns a string of html with the given content type. E.g.,
897 /contenttype?text/css returns an html file with the Content-Type
898 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000899 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000900 return False
901 query_char = self.path.find('?')
902 content_type = self.path[query_char + 1:].strip()
903 if not content_type:
904 content_type = 'text/html'
905 self.send_response(200)
906 self.send_header('Content-Type', content_type)
907 self.end_headers()
908 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
909 return True
910
911 def ServerRedirectHandler(self):
912 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000913 '/server-redirect?http://foo.bar/asdf' to redirect to
914 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000915
916 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000917 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000918 return False
919
920 query_char = self.path.find('?')
921 if query_char < 0 or len(self.path) <= query_char + 1:
922 self.sendRedirectHelp(test_name)
923 return True
924 dest = self.path[query_char + 1:]
925
926 self.send_response(301) # moved permanently
927 self.send_header('Location', dest)
928 self.send_header('Content-type', 'text/html')
929 self.end_headers()
930 self.wfile.write('<html><head>')
931 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
932
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000933 return True
initial.commit94958cf2008-07-26 22:42:52 +0000934
935 def ClientRedirectHandler(self):
936 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000937 '/client-redirect?http://foo.bar/asdf' to redirect to
938 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000939
940 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000941 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000942 return False
943
944 query_char = self.path.find('?');
945 if query_char < 0 or len(self.path) <= query_char + 1:
946 self.sendRedirectHelp(test_name)
947 return True
948 dest = self.path[query_char + 1:]
949
950 self.send_response(200)
951 self.send_header('Content-type', 'text/html')
952 self.end_headers()
953 self.wfile.write('<html><head>')
954 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
955 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
956
957 return True
958
959 def DefaultResponseHandler(self):
960 """This is the catch-all response handler for requests that aren't handled
961 by one of the special handlers above.
962 Note that we specify the content-length as without it the https connection
963 is not closed properly (and the browser keeps expecting data)."""
964
965 contents = "Default response given for path: " + self.path
966 self.send_response(200)
967 self.send_header('Content-type', 'text/html')
968 self.send_header("Content-Length", len(contents))
969 self.end_headers()
970 self.wfile.write(contents)
971 return True
972
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000973 def RedirectConnectHandler(self):
974 """Sends a redirect to the CONNECT request for www.redirect.com. This
975 response is not specified by the RFC, so the browser should not follow
976 the redirect."""
977
978 if (self.path.find("www.redirect.com") < 0):
979 return False
980
981 dest = "http://www.destination.com/foo.js"
982
983 self.send_response(302) # moved temporarily
984 self.send_header('Location', dest)
985 self.send_header('Connection', 'close')
986 self.end_headers()
987 return True
988
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000989 def ServerAuthConnectHandler(self):
990 """Sends a 401 to the CONNECT request for www.server-auth.com. This
991 response doesn't make sense because the proxy server cannot request
992 server authentication."""
993
994 if (self.path.find("www.server-auth.com") < 0):
995 return False
996
997 challenge = 'Basic realm="WallyWorld"'
998
999 self.send_response(401) # unauthorized
1000 self.send_header('WWW-Authenticate', challenge)
1001 self.send_header('Connection', 'close')
1002 self.end_headers()
1003 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001004
1005 def DefaultConnectResponseHandler(self):
1006 """This is the catch-all response handler for CONNECT requests that aren't
1007 handled by one of the special handlers above. Real Web servers respond
1008 with 400 to CONNECT requests."""
1009
1010 contents = "Your client has issued a malformed or illegal request."
1011 self.send_response(400) # bad request
1012 self.send_header('Content-type', 'text/html')
1013 self.send_header("Content-Length", len(contents))
1014 self.end_headers()
1015 self.wfile.write(contents)
1016 return True
1017
1018 def do_CONNECT(self):
1019 for handler in self._connect_handlers:
1020 if handler():
1021 return
1022
initial.commit94958cf2008-07-26 22:42:52 +00001023 def do_GET(self):
1024 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001025 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001026 return
1027
1028 def do_POST(self):
1029 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001030 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001031 return
1032
1033 # called by the redirect handling function when there is no parameter
1034 def sendRedirectHelp(self, redirect_name):
1035 self.send_response(200)
1036 self.send_header('Content-type', 'text/html')
1037 self.end_headers()
1038 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1039 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1040 self.wfile.write('</body></html>')
1041
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001042def MakeDumpDir(data_dir):
1043 """Create directory named 'dump' where uploaded data via HTTP POST request
1044 will be stored. If the directory already exists all files and subdirectories
1045 will be deleted."""
1046 dump_dir = os.path.join(data_dir, 'dump');
1047 if os.path.isdir(dump_dir):
1048 shutil.rmtree(dump_dir)
1049 os.mkdir(dump_dir)
1050
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001051def MakeDataDir():
1052 if options.data_dir:
1053 if not os.path.isdir(options.data_dir):
1054 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1055 return None
1056 my_data_dir = options.data_dir
1057 else:
1058 # Create the default path to our data dir, relative to the exe dir.
1059 my_data_dir = os.path.dirname(sys.argv[0])
1060 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1061 "test", "data")
1062
1063 #TODO(ibrar): Must use Find* funtion defined in google\tools
1064 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1065
1066 return my_data_dir
1067
initial.commit94958cf2008-07-26 22:42:52 +00001068def main(options, args):
1069 # redirect output to a log file so it doesn't spam the unit test output
1070 logfile = open('testserver.log', 'w')
1071 sys.stderr = sys.stdout = logfile
1072
1073 port = options.port
1074
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001075 if options.server_type == SERVER_HTTP:
1076 if options.cert:
1077 # let's make sure the cert file exists.
1078 if not os.path.isfile(options.cert):
1079 print 'specified cert file not found: ' + options.cert + ' exiting...'
1080 return
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001081 if options.forking:
1082 server_class = ForkingHTTPSServer
1083 else:
1084 server_class = HTTPSServer
1085 server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001086 print 'HTTPS server started on port %d...' % port
1087 else:
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001088 if options.forking:
1089 server_class = ForkingHTTPServer
1090 else:
1091 server_class = StoppableHTTPServer
1092 server = server_class(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001093 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001094
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001095 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001096 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001097 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001098
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001099 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001100 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001101 my_data_dir = MakeDataDir()
1102
1103 def line_logger(msg):
1104 if (msg.find("kill") >= 0):
1105 server.stop = True
1106 print 'shutting down server'
1107 sys.exit(0)
1108
1109 # Instantiate a dummy authorizer for managing 'virtual' users
1110 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1111
1112 # Define a new user having full r/w permissions and a read-only
1113 # anonymous user
1114 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1115
1116 authorizer.add_anonymous(my_data_dir)
1117
1118 # Instantiate FTP handler class
1119 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1120 ftp_handler.authorizer = authorizer
1121 pyftpdlib.ftpserver.logline = line_logger
1122
1123 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001124 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1125 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001126
1127 # Instantiate FTP server class and listen to 127.0.0.1:port
1128 address = ('127.0.0.1', port)
1129 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1130 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001131
1132 try:
1133 server.serve_forever()
1134 except KeyboardInterrupt:
1135 print 'shutting down server'
1136 server.stop = True
1137
1138if __name__ == '__main__':
1139 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001140 option_parser.add_option("-f", '--ftp', action='store_const',
1141 const=SERVER_FTP, default=SERVER_HTTP,
1142 dest='server_type',
1143 help='FTP or HTTP server default HTTP')
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001144 option_parser.add_option('--forking', action='store_true', default=False,
1145 dest='forking',
1146 help='Serve each request in a separate process')
initial.commit94958cf2008-07-26 22:42:52 +00001147 option_parser.add_option('', '--port', default='8888', type='int',
1148 help='Port used by the server')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001149 option_parser.add_option('', '--data-dir', dest='data_dir',
initial.commit94958cf2008-07-26 22:42:52 +00001150 help='Directory from which to read the files')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001151 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001152 help='Specify that https should be used, specify '
1153 'the path to the cert containing the private key '
1154 'the server should use')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001155 option_parser.add_option('', '--file-root-url', default='/files/',
1156 help='Specify a root URL for files served.')
initial.commit94958cf2008-07-26 22:42:52 +00001157 options, args = option_parser.parse_args()
1158
1159 sys.exit(main(options, args))