blob: 0d4a4308fff1689040c0e32f9ab54f59744b9dc1 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00002# Copyright (c) 2011 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
initial.commit94958cf2008-07-26 22:42:52 +000021import optparse
22import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000023import random
initial.commit94958cf2008-07-26 22:42:52 +000024import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000025import select
initial.commit94958cf2008-07-26 22:42:52 +000026import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import socket
initial.commit94958cf2008-07-26 22:42:52 +000028import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000029import struct
initial.commit94958cf2008-07-26 22:42:52 +000030import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000031import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000032import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000033import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000034import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000035
36# Ignore deprecation warnings, they make our output more cluttered.
37warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000039import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000040import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000041import tlslite
42import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000044try:
45 import hashlib
46 _new_md5 = hashlib.md5
47except ImportError:
48 import md5
49 _new_md5 = md5.new
50
dpranke@chromium.org70049b72011-10-14 00:38:18 +000051try:
52 import json
53except ImportError:
54 import simplejson as json
55
davidben@chromium.org06fcf202010-09-22 18:15:23 +000056if sys.platform == 'win32':
57 import msvcrt
58
maruel@chromium.org756cf982009-03-05 12:46:38 +000059SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000060SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000061SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000062SERVER_TCP_ECHO = 3
63SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000064
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000065# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000066debug_output = sys.stderr
67def debug(str):
68 debug_output.write(str + "\n")
69 debug_output.flush()
70
71class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
72 """This is a specialization of of BaseHTTPServer to allow it
73 to be exited cleanly (by setting its "stop" member to True)."""
74
75 def serve_forever(self):
76 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000077 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000078 while not self.stop:
79 self.handle_request()
80 self.socket.close()
81
82class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
83 """This is a specialization of StoppableHTTPerver that add https support."""
84
davidben@chromium.org31282a12010-08-07 01:10:02 +000085 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000086 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000087 s = open(cert_path).read()
88 x509 = tlslite.api.X509()
89 x509.parse(s)
90 self.cert_chain = tlslite.api.X509CertChain([x509])
91 s = open(cert_path).read()
92 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000093 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000094 self.ssl_client_cas = []
95 for ca_file in ssl_client_cas:
96 s = open(ca_file).read()
97 x509 = tlslite.api.X509()
98 x509.parse(s)
99 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000100 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
101 if ssl_bulk_ciphers is not None:
102 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000103
104 self.session_cache = tlslite.api.SessionCache()
105 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
106
107 def handshake(self, tlsConnection):
108 """Creates the SSL connection."""
109 try:
110 tlsConnection.handshakeServer(certChain=self.cert_chain,
111 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000112 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000113 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000114 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000115 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000116 tlsConnection.ignoreAbruptClose = True
117 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000118 except tlslite.api.TLSAbruptCloseError:
119 # Ignore abrupt close.
120 return True
initial.commit94958cf2008-07-26 22:42:52 +0000121 except tlslite.api.TLSError, error:
122 print "Handshake failure:", str(error)
123 return False
124
akalin@chromium.org154bb132010-11-12 02:20:27 +0000125
126class SyncHTTPServer(StoppableHTTPServer):
127 """An HTTP server that handles sync commands."""
128
129 def __init__(self, server_address, request_handler_class):
130 # We import here to avoid pulling in chromiumsync's dependencies
131 # unless strictly necessary.
132 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000133 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000134 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000135 self._sync_handler = chromiumsync.TestServer()
136 self._xmpp_socket_map = {}
137 self._xmpp_server = xmppserver.XmppServer(
138 self._xmpp_socket_map, ('localhost', 0))
139 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000140 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000141
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000142 def GetXmppServer(self):
143 return self._xmpp_server
144
akalin@chromium.org154bb132010-11-12 02:20:27 +0000145 def HandleCommand(self, query, raw_request):
146 return self._sync_handler.HandleCommand(query, raw_request)
147
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000148 def HandleRequestNoBlock(self):
149 """Handles a single request.
150
151 Copied from SocketServer._handle_request_noblock().
152 """
153 try:
154 request, client_address = self.get_request()
155 except socket.error:
156 return
157 if self.verify_request(request, client_address):
158 try:
159 self.process_request(request, client_address)
160 except:
161 self.handle_error(request, client_address)
162 self.close_request(request)
163
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000164 def SetAuthenticated(self, auth_valid):
165 self.authenticated = auth_valid
166
167 def GetAuthenticated(self):
168 return self.authenticated
169
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000170 def serve_forever(self):
171 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
172 """
173
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000174 def HandleXmppSocket(fd, socket_map, handler):
175 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000176
177 Adapted from asyncore.read() et al.
178 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000179 xmpp_connection = socket_map.get(fd)
180 # This could happen if a previous handler call caused fd to get
181 # removed from socket_map.
182 if xmpp_connection is None:
183 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000184 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000185 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000186 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
187 raise
188 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000189 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000190
191 while True:
192 read_fds = [ self.fileno() ]
193 write_fds = []
194 exceptional_fds = []
195
196 for fd, xmpp_connection in self._xmpp_socket_map.items():
197 is_r = xmpp_connection.readable()
198 is_w = xmpp_connection.writable()
199 if is_r:
200 read_fds.append(fd)
201 if is_w:
202 write_fds.append(fd)
203 if is_r or is_w:
204 exceptional_fds.append(fd)
205
206 try:
207 read_fds, write_fds, exceptional_fds = (
208 select.select(read_fds, write_fds, exceptional_fds))
209 except select.error, err:
210 if err.args[0] != errno.EINTR:
211 raise
212 else:
213 continue
214
215 for fd in read_fds:
216 if fd == self.fileno():
217 self.HandleRequestNoBlock()
218 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000219 HandleXmppSocket(fd, self._xmpp_socket_map,
220 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000221
222 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000223 HandleXmppSocket(fd, self._xmpp_socket_map,
224 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000225
226 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000227 HandleXmppSocket(fd, self._xmpp_socket_map,
228 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000229
akalin@chromium.org154bb132010-11-12 02:20:27 +0000230
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000231class TCPEchoServer(SocketServer.TCPServer):
232 """A TCP echo server that echoes back what it has received."""
233
234 def server_bind(self):
235 """Override server_bind to store the server name."""
236 SocketServer.TCPServer.server_bind(self)
237 host, port = self.socket.getsockname()[:2]
238 self.server_name = socket.getfqdn(host)
239 self.server_port = port
240
241 def serve_forever(self):
242 self.stop = False
243 self.nonce_time = None
244 while not self.stop:
245 self.handle_request()
246 self.socket.close()
247
248
249class UDPEchoServer(SocketServer.UDPServer):
250 """A UDP echo server that echoes back what it has received."""
251
252 def server_bind(self):
253 """Override server_bind to store the server name."""
254 SocketServer.UDPServer.server_bind(self)
255 host, port = self.socket.getsockname()[:2]
256 self.server_name = socket.getfqdn(host)
257 self.server_port = port
258
259 def serve_forever(self):
260 self.stop = False
261 self.nonce_time = None
262 while not self.stop:
263 self.handle_request()
264 self.socket.close()
265
266
akalin@chromium.org154bb132010-11-12 02:20:27 +0000267class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
268
269 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000270 connect_handlers, get_handlers, head_handlers, post_handlers,
271 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000272 self._connect_handlers = connect_handlers
273 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000274 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000275 self._post_handlers = post_handlers
276 self._put_handlers = put_handlers
277 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
278 self, request, client_address, socket_server)
279
280 def log_request(self, *args, **kwargs):
281 # Disable request logging to declutter test log output.
282 pass
283
284 def _ShouldHandleRequest(self, handler_name):
285 """Determines if the path can be handled by the handler.
286
287 We consider a handler valid if the path begins with the
288 handler name. It can optionally be followed by "?*", "/*".
289 """
290
291 pattern = re.compile('%s($|\?|/).*' % handler_name)
292 return pattern.match(self.path)
293
294 def do_CONNECT(self):
295 for handler in self._connect_handlers:
296 if handler():
297 return
298
299 def do_GET(self):
300 for handler in self._get_handlers:
301 if handler():
302 return
303
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000304 def do_HEAD(self):
305 for handler in self._head_handlers:
306 if handler():
307 return
308
akalin@chromium.org154bb132010-11-12 02:20:27 +0000309 def do_POST(self):
310 for handler in self._post_handlers:
311 if handler():
312 return
313
314 def do_PUT(self):
315 for handler in self._put_handlers:
316 if handler():
317 return
318
319
320class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000321
322 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000323 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000324 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000325 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000326 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000327 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000328 self.NoCacheMaxAgeTimeHandler,
329 self.NoCacheTimeHandler,
330 self.CacheTimeHandler,
331 self.CacheExpiresHandler,
332 self.CacheProxyRevalidateHandler,
333 self.CachePrivateHandler,
334 self.CachePublicHandler,
335 self.CacheSMaxAgeHandler,
336 self.CacheMustRevalidateHandler,
337 self.CacheMustRevalidateMaxAgeHandler,
338 self.CacheNoStoreHandler,
339 self.CacheNoStoreMaxAgeHandler,
340 self.CacheNoTransformHandler,
341 self.DownloadHandler,
342 self.DownloadFinishHandler,
343 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000344 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000345 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000346 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000347 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000348 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000349 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000350 self.AuthBasicHandler,
351 self.AuthDigestHandler,
352 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000353 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000354 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000355 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000356 self.ServerRedirectHandler,
357 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000358 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000359 self.MultipartSlowHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000360 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000361 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000362 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000363 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000364 self.DeviceManagementHandler] + get_handlers
365 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000366 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000367 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000368 head_handlers = [
369 self.FileHandler,
370 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000371
maruel@google.come250a9b2009-03-10 17:39:46 +0000372 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000373 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000374 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000375 'gif': 'image/gif',
376 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000377 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000378 'pdf' : 'application/pdf',
379 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000380 }
initial.commit94958cf2008-07-26 22:42:52 +0000381 self._default_mime_type = 'text/html'
382
akalin@chromium.org154bb132010-11-12 02:20:27 +0000383 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000384 connect_handlers, get_handlers, head_handlers,
385 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000386
initial.commit94958cf2008-07-26 22:42:52 +0000387 def GetMIMETypeFromName(self, file_name):
388 """Returns the mime type for the specified file_name. So far it only looks
389 at the file extension."""
390
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000391 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000392 if len(extension) == 0:
393 # no extension.
394 return self._default_mime_type
395
ericroman@google.comc17ca532009-05-07 03:51:05 +0000396 # extension starts with a dot, so we need to remove it
397 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000398
initial.commit94958cf2008-07-26 22:42:52 +0000399 def NoCacheMaxAgeTimeHandler(self):
400 """This request handler yields a page with the title set to the current
401 system time, and no caching requested."""
402
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000403 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000404 return False
405
406 self.send_response(200)
407 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000408 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.end_headers()
410
maruel@google.come250a9b2009-03-10 17:39:46 +0000411 self.wfile.write('<html><head><title>%s</title></head></html>' %
412 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000413
414 return True
415
416 def NoCacheTimeHandler(self):
417 """This request handler yields a page with the title set to the current
418 system time, and no caching requested."""
419
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000420 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000421 return False
422
423 self.send_response(200)
424 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000425 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000426 self.end_headers()
427
maruel@google.come250a9b2009-03-10 17:39:46 +0000428 self.wfile.write('<html><head><title>%s</title></head></html>' %
429 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000430
431 return True
432
433 def CacheTimeHandler(self):
434 """This request handler yields a page with the title set to the current
435 system time, and allows caching for one minute."""
436
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000437 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000438 return False
439
440 self.send_response(200)
441 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000442 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.end_headers()
444
maruel@google.come250a9b2009-03-10 17:39:46 +0000445 self.wfile.write('<html><head><title>%s</title></head></html>' %
446 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000447
448 return True
449
450 def CacheExpiresHandler(self):
451 """This request handler yields a page with the title set to the current
452 system time, and set the page to expire on 1 Jan 2099."""
453
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000454 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000455 return False
456
457 self.send_response(200)
458 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000459 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000460 self.end_headers()
461
maruel@google.come250a9b2009-03-10 17:39:46 +0000462 self.wfile.write('<html><head><title>%s</title></head></html>' %
463 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000464
465 return True
466
467 def CacheProxyRevalidateHandler(self):
468 """This request handler yields a page with the title set to the current
469 system time, and allows caching for 60 seconds"""
470
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000471 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000472 return False
473
474 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000475 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000476 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
477 self.end_headers()
478
maruel@google.come250a9b2009-03-10 17:39:46 +0000479 self.wfile.write('<html><head><title>%s</title></head></html>' %
480 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000481
482 return True
483
484 def CachePrivateHandler(self):
485 """This request handler yields a page with the title set to the current
486 system time, and allows caching for 5 seconds."""
487
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000488 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000489 return False
490
491 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000492 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000493 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000494 self.end_headers()
495
maruel@google.come250a9b2009-03-10 17:39:46 +0000496 self.wfile.write('<html><head><title>%s</title></head></html>' %
497 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000498
499 return True
500
501 def CachePublicHandler(self):
502 """This request handler yields a page with the title set to the current
503 system time, and allows caching for 5 seconds."""
504
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000505 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000506 return False
507
508 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000509 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000510 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000511 self.end_headers()
512
maruel@google.come250a9b2009-03-10 17:39:46 +0000513 self.wfile.write('<html><head><title>%s</title></head></html>' %
514 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000515
516 return True
517
518 def CacheSMaxAgeHandler(self):
519 """This request handler yields a page with the title set to the current
520 system time, and does not allow for caching."""
521
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000522 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000523 return False
524
525 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000526 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000527 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
528 self.end_headers()
529
maruel@google.come250a9b2009-03-10 17:39:46 +0000530 self.wfile.write('<html><head><title>%s</title></head></html>' %
531 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000532
533 return True
534
535 def CacheMustRevalidateHandler(self):
536 """This request handler yields a page with the title set to the current
537 system time, and does not allow caching."""
538
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000539 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000540 return False
541
542 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000543 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000544 self.send_header('Cache-Control', 'must-revalidate')
545 self.end_headers()
546
maruel@google.come250a9b2009-03-10 17:39:46 +0000547 self.wfile.write('<html><head><title>%s</title></head></html>' %
548 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000549
550 return True
551
552 def CacheMustRevalidateMaxAgeHandler(self):
553 """This request handler yields a page with the title set to the current
554 system time, and does not allow caching event though max-age of 60
555 seconds is specified."""
556
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000557 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000558 return False
559
560 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000561 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000562 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
563 self.end_headers()
564
maruel@google.come250a9b2009-03-10 17:39:46 +0000565 self.wfile.write('<html><head><title>%s</title></head></html>' %
566 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000567
568 return True
569
initial.commit94958cf2008-07-26 22:42:52 +0000570 def CacheNoStoreHandler(self):
571 """This request handler yields a page with the title set to the current
572 system time, and does not allow the page to be stored."""
573
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000574 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000575 return False
576
577 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000578 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000579 self.send_header('Cache-Control', 'no-store')
580 self.end_headers()
581
maruel@google.come250a9b2009-03-10 17:39:46 +0000582 self.wfile.write('<html><head><title>%s</title></head></html>' %
583 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000584
585 return True
586
587 def CacheNoStoreMaxAgeHandler(self):
588 """This request handler yields a page with the title set to the current
589 system time, and does not allow the page to be stored even though max-age
590 of 60 seconds is specified."""
591
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000592 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000593 return False
594
595 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000596 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000597 self.send_header('Cache-Control', 'max-age=60, no-store')
598 self.end_headers()
599
maruel@google.come250a9b2009-03-10 17:39:46 +0000600 self.wfile.write('<html><head><title>%s</title></head></html>' %
601 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000602
603 return True
604
605
606 def CacheNoTransformHandler(self):
607 """This request handler yields a page with the title set to the current
608 system time, and does not allow the content to transformed during
609 user-agent caching"""
610
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000611 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000612 return False
613
614 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000615 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000616 self.send_header('Cache-Control', 'no-transform')
617 self.end_headers()
618
maruel@google.come250a9b2009-03-10 17:39:46 +0000619 self.wfile.write('<html><head><title>%s</title></head></html>' %
620 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000621
622 return True
623
624 def EchoHeader(self):
625 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000626 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000627
ananta@chromium.org56812d02011-04-07 17:52:05 +0000628 """This function echoes back the value of a specific request header"""
629 """while allowing caching for 16 hours."""
630 def EchoHeaderCache(self):
631 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000632
633 def EchoHeaderHelper(self, echo_header):
634 """This function echoes back the value of the request header passed in."""
635 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000636 return False
637
638 query_char = self.path.find('?')
639 if query_char != -1:
640 header_name = self.path[query_char+1:]
641
642 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000643 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000644 if echo_header == '/echoheadercache':
645 self.send_header('Cache-control', 'max-age=60000')
646 else:
647 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000648 # insert a vary header to properly indicate that the cachability of this
649 # request is subject to value of the request header being echoed.
650 if len(header_name) > 0:
651 self.send_header('Vary', header_name)
652 self.end_headers()
653
654 if len(header_name) > 0:
655 self.wfile.write(self.headers.getheader(header_name))
656
657 return True
658
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000659 def ReadRequestBody(self):
660 """This function reads the body of the current HTTP request, handling
661 both plain and chunked transfer encoded requests."""
662
663 if self.headers.getheader('transfer-encoding') != 'chunked':
664 length = int(self.headers.getheader('content-length'))
665 return self.rfile.read(length)
666
667 # Read the request body as chunks.
668 body = ""
669 while True:
670 line = self.rfile.readline()
671 length = int(line, 16)
672 if length == 0:
673 self.rfile.readline()
674 break
675 body += self.rfile.read(length)
676 self.rfile.read(2)
677 return body
678
initial.commit94958cf2008-07-26 22:42:52 +0000679 def EchoHandler(self):
680 """This handler just echoes back the payload of the request, for testing
681 form submission."""
682
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000683 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000684 return False
685
686 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000687 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000688 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000689 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000690 return True
691
692 def EchoTitleHandler(self):
693 """This handler is like Echo, but sets the page title to the request."""
694
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000695 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000696 return False
697
698 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000699 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000700 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000701 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000702 self.wfile.write('<html><head><title>')
703 self.wfile.write(request)
704 self.wfile.write('</title></head></html>')
705 return True
706
707 def EchoAllHandler(self):
708 """This handler yields a (more) human-readable page listing information
709 about the request header & contents."""
710
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000711 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000712 return False
713
714 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000715 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000716 self.end_headers()
717 self.wfile.write('<html><head><style>'
718 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
719 '</style></head><body>'
720 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000721 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000722 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000723
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000724 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000725 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000726 params = cgi.parse_qs(qs, keep_blank_values=1)
727
728 for param in params:
729 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000730
731 self.wfile.write('</pre>')
732
733 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
734
735 self.wfile.write('</body></html>')
736 return True
737
738 def DownloadHandler(self):
739 """This handler sends a downloadable file with or without reporting
740 the size (6K)."""
741
742 if self.path.startswith("/download-unknown-size"):
743 send_length = False
744 elif self.path.startswith("/download-known-size"):
745 send_length = True
746 else:
747 return False
748
749 #
750 # The test which uses this functionality is attempting to send
751 # small chunks of data to the client. Use a fairly large buffer
752 # so that we'll fill chrome's IO buffer enough to force it to
753 # actually write the data.
754 # See also the comments in the client-side of this test in
755 # download_uitest.cc
756 #
757 size_chunk1 = 35*1024
758 size_chunk2 = 10*1024
759
760 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000761 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000762 self.send_header('Cache-Control', 'max-age=0')
763 if send_length:
764 self.send_header('Content-Length', size_chunk1 + size_chunk2)
765 self.end_headers()
766
767 # First chunk of data:
768 self.wfile.write("*" * size_chunk1)
769 self.wfile.flush()
770
771 # handle requests until one of them clears this flag.
772 self.server.waitForDownload = True
773 while self.server.waitForDownload:
774 self.server.handle_request()
775
776 # Second chunk of data:
777 self.wfile.write("*" * size_chunk2)
778 return True
779
780 def DownloadFinishHandler(self):
781 """This handler just tells the server to finish the current download."""
782
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000783 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000784 return False
785
786 self.server.waitForDownload = False
787 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000788 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000789 self.send_header('Cache-Control', 'max-age=0')
790 self.end_headers()
791 return True
792
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000793 def _ReplaceFileData(self, data, query_parameters):
794 """Replaces matching substrings in a file.
795
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000796 If the 'replace_text' URL query parameter is present, it is expected to be
797 of the form old_text:new_text, which indicates that any old_text strings in
798 the file are replaced with new_text. Multiple 'replace_text' parameters may
799 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000800
801 If the parameters are not present, |data| is returned.
802 """
803 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000804 replace_text_values = query_dict.get('replace_text', [])
805 for replace_text_value in replace_text_values:
806 replace_text_args = replace_text_value.split(':')
807 if len(replace_text_args) != 2:
808 raise ValueError(
809 'replace_text must be of form old_text:new_text. Actual value: %s' %
810 replace_text_value)
811 old_text_b64, new_text_b64 = replace_text_args
812 old_text = base64.urlsafe_b64decode(old_text_b64)
813 new_text = base64.urlsafe_b64decode(new_text_b64)
814 data = data.replace(old_text, new_text)
815 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000816
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000817 def ZipFileHandler(self):
818 """This handler sends the contents of the requested file in compressed form.
819 Can pass in a parameter that specifies that the content length be
820 C - the compressed size (OK),
821 U - the uncompressed size (Non-standard, but handled),
822 S - less than compressed (OK because we keep going),
823 M - larger than compressed but less than uncompressed (an error),
824 L - larger than uncompressed (an error)
825 Example: compressedfiles/Picture_1.doc?C
826 """
827
828 prefix = "/compressedfiles/"
829 if not self.path.startswith(prefix):
830 return False
831
832 # Consume a request body if present.
833 if self.command == 'POST' or self.command == 'PUT' :
834 self.ReadRequestBody()
835
836 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
837
838 if not query in ('C', 'U', 'S', 'M', 'L'):
839 return False
840
841 sub_path = url_path[len(prefix):]
842 entries = sub_path.split('/')
843 file_path = os.path.join(self.server.data_dir, *entries)
844 if os.path.isdir(file_path):
845 file_path = os.path.join(file_path, 'index.html')
846
847 if not os.path.isfile(file_path):
848 print "File not found " + sub_path + " full path:" + file_path
849 self.send_error(404)
850 return True
851
852 f = open(file_path, "rb")
853 data = f.read()
854 uncompressed_len = len(data)
855 f.close()
856
857 # Compress the data.
858 data = zlib.compress(data)
859 compressed_len = len(data)
860
861 content_length = compressed_len
862 if query == 'U':
863 content_length = uncompressed_len
864 elif query == 'S':
865 content_length = compressed_len / 2
866 elif query == 'M':
867 content_length = (compressed_len + uncompressed_len) / 2
868 elif query == 'L':
869 content_length = compressed_len + uncompressed_len
870
871 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000872 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000873 self.send_header('Content-encoding', 'deflate')
874 self.send_header('Connection', 'close')
875 self.send_header('Content-Length', content_length)
876 self.send_header('ETag', '\'' + file_path + '\'')
877 self.end_headers()
878
879 self.wfile.write(data)
880
881 return True
882
initial.commit94958cf2008-07-26 22:42:52 +0000883 def FileHandler(self):
884 """This handler sends the contents of the requested file. Wow, it's like
885 a real webserver!"""
886
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000887 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000888 if not self.path.startswith(prefix):
889 return False
890
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000891 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000892 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000893 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000894
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000895 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
896 sub_path = url_path[len(prefix):]
897 entries = sub_path.split('/')
898 file_path = os.path.join(self.server.data_dir, *entries)
899 if os.path.isdir(file_path):
900 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000901
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000902 if not os.path.isfile(file_path):
903 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000904 self.send_error(404)
905 return True
906
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000907 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000908 data = f.read()
909 f.close()
910
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000911 data = self._ReplaceFileData(data, query)
912
initial.commit94958cf2008-07-26 22:42:52 +0000913 # If file.mock-http-headers exists, it contains the headers we
914 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000915 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000916 if os.path.isfile(headers_path):
917 f = open(headers_path, "r")
918
919 # "HTTP/1.1 200 OK"
920 response = f.readline()
921 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
922 self.send_response(int(status_code))
923
924 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000925 header_values = re.findall('(\S+):\s*(.*)', line)
926 if len(header_values) > 0:
927 # "name: value"
928 name, value = header_values[0]
929 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000930 f.close()
931 else:
932 # Could be more generic once we support mime-type sniffing, but for
933 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000934
935 range = self.headers.get('Range')
936 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +0000937 # Note this doesn't handle all valid byte range values (i.e. left
938 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +0000939 range = range[6:].split('-')
940 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000941 if range[1]:
942 end = int(range[1])
943 else:
944 end = len(data)
jam@chromium.org41550782010-11-17 23:47:50 +0000945
946 self.send_response(206)
947 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
948 str(len(data))
949 self.send_header('Content-Range', content_range)
950 data = data[start: end + 1]
951 else:
952 self.send_response(200)
953
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000954 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000955 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000956 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000957 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000958 self.end_headers()
959
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000960 if (self.command != 'HEAD'):
961 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000962
963 return True
964
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000965 def SetCookieHandler(self):
966 """This handler just sets a cookie, for testing cookie handling."""
967
968 if not self._ShouldHandleRequest("/set-cookie"):
969 return False
970
971 query_char = self.path.find('?')
972 if query_char != -1:
973 cookie_values = self.path[query_char + 1:].split('&')
974 else:
975 cookie_values = ("",)
976 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000977 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000978 for cookie_value in cookie_values:
979 self.send_header('Set-Cookie', '%s' % cookie_value)
980 self.end_headers()
981 for cookie_value in cookie_values:
982 self.wfile.write('%s' % cookie_value)
983 return True
984
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000985 def SetHeaderHandler(self):
986 """This handler sets a response header. Parameters are in the
987 key%3A%20value&key2%3A%20value2 format."""
988
989 if not self._ShouldHandleRequest("/set-header"):
990 return False
991
992 query_char = self.path.find('?')
993 if query_char != -1:
994 headers_values = self.path[query_char + 1:].split('&')
995 else:
996 headers_values = ("",)
997 self.send_response(200)
998 self.send_header('Content-Type', 'text/html')
999 for header_value in headers_values:
1000 header_value = urllib.unquote(header_value)
1001 (key, value) = header_value.split(': ', 1)
1002 self.send_header(key, value)
1003 self.end_headers()
1004 for header_value in headers_values:
1005 self.wfile.write('%s' % header_value)
1006 return True
1007
initial.commit94958cf2008-07-26 22:42:52 +00001008 def AuthBasicHandler(self):
1009 """This handler tests 'Basic' authentication. It just sends a page with
1010 title 'user/pass' if you succeed."""
1011
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001012 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001013 return False
1014
1015 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001016 expected_password = 'secret'
1017 realm = 'testrealm'
1018 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001019
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001020 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1021 query_params = cgi.parse_qs(query, True)
1022 if 'set-cookie-if-challenged' in query_params:
1023 set_cookie_if_challenged = True
1024 if 'password' in query_params:
1025 expected_password = query_params['password'][0]
1026 if 'realm' in query_params:
1027 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001028
initial.commit94958cf2008-07-26 22:42:52 +00001029 auth = self.headers.getheader('authorization')
1030 try:
1031 if not auth:
1032 raise Exception('no auth')
1033 b64str = re.findall(r'Basic (\S+)', auth)[0]
1034 userpass = base64.b64decode(b64str)
1035 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001036 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001037 raise Exception('wrong password')
1038 except Exception, e:
1039 # Authentication failed.
1040 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001041 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001042 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001043 if set_cookie_if_challenged:
1044 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001045 self.end_headers()
1046 self.wfile.write('<html><head>')
1047 self.wfile.write('<title>Denied: %s</title>' % e)
1048 self.wfile.write('</head><body>')
1049 self.wfile.write('auth=%s<p>' % auth)
1050 self.wfile.write('b64str=%s<p>' % b64str)
1051 self.wfile.write('username: %s<p>' % username)
1052 self.wfile.write('userpass: %s<p>' % userpass)
1053 self.wfile.write('password: %s<p>' % password)
1054 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1055 self.wfile.write('</body></html>')
1056 return True
1057
1058 # Authentication successful. (Return a cachable response to allow for
1059 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001060 old_protocol_version = self.protocol_version
1061 self.protocol_version = "HTTP/1.1"
1062
initial.commit94958cf2008-07-26 22:42:52 +00001063 if_none_match = self.headers.getheader('if-none-match')
1064 if if_none_match == "abc":
1065 self.send_response(304)
1066 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001067 elif url_path.endswith(".gif"):
1068 # Using chrome/test/data/google/logo.gif as the test image
1069 test_image_path = ['google', 'logo.gif']
1070 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1071 if not os.path.isfile(gif_path):
1072 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001073 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001074 return True
1075
1076 f = open(gif_path, "rb")
1077 data = f.read()
1078 f.close()
1079
1080 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001081 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001082 self.send_header('Cache-control', 'max-age=60000')
1083 self.send_header('Etag', 'abc')
1084 self.end_headers()
1085 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001086 else:
1087 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001088 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001089 self.send_header('Cache-control', 'max-age=60000')
1090 self.send_header('Etag', 'abc')
1091 self.end_headers()
1092 self.wfile.write('<html><head>')
1093 self.wfile.write('<title>%s/%s</title>' % (username, password))
1094 self.wfile.write('</head><body>')
1095 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001096 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001097 self.wfile.write('</body></html>')
1098
rvargas@google.com54453b72011-05-19 01:11:11 +00001099 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001100 return True
1101
tonyg@chromium.org75054202010-03-31 22:06:10 +00001102 def GetNonce(self, force_reset=False):
1103 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001104
tonyg@chromium.org75054202010-03-31 22:06:10 +00001105 This is a fake implementation. A real implementation would only use a given
1106 nonce a single time (hence the name n-once). However, for the purposes of
1107 unittesting, we don't care about the security of the nonce.
1108
1109 Args:
1110 force_reset: Iff set, the nonce will be changed. Useful for testing the
1111 "stale" response.
1112 """
1113 if force_reset or not self.server.nonce_time:
1114 self.server.nonce_time = time.time()
1115 return _new_md5('privatekey%s%d' %
1116 (self.path, self.server.nonce_time)).hexdigest()
1117
1118 def AuthDigestHandler(self):
1119 """This handler tests 'Digest' authentication.
1120
1121 It just sends a page with title 'user/pass' if you succeed.
1122
1123 A stale response is sent iff "stale" is present in the request path.
1124 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001125 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001126 return False
1127
tonyg@chromium.org75054202010-03-31 22:06:10 +00001128 stale = 'stale' in self.path
1129 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001130 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001131 password = 'secret'
1132 realm = 'testrealm'
1133
1134 auth = self.headers.getheader('authorization')
1135 pairs = {}
1136 try:
1137 if not auth:
1138 raise Exception('no auth')
1139 if not auth.startswith('Digest'):
1140 raise Exception('not digest')
1141 # Pull out all the name="value" pairs as a dictionary.
1142 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1143
1144 # Make sure it's all valid.
1145 if pairs['nonce'] != nonce:
1146 raise Exception('wrong nonce')
1147 if pairs['opaque'] != opaque:
1148 raise Exception('wrong opaque')
1149
1150 # Check the 'response' value and make sure it matches our magic hash.
1151 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001152 hash_a1 = _new_md5(
1153 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001154 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001155 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001156 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001157 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1158 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001159 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001160
1161 if pairs['response'] != response:
1162 raise Exception('wrong password')
1163 except Exception, e:
1164 # Authentication failed.
1165 self.send_response(401)
1166 hdr = ('Digest '
1167 'realm="%s", '
1168 'domain="/", '
1169 'qop="auth", '
1170 'algorithm=MD5, '
1171 'nonce="%s", '
1172 'opaque="%s"') % (realm, nonce, opaque)
1173 if stale:
1174 hdr += ', stale="TRUE"'
1175 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001176 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001177 self.end_headers()
1178 self.wfile.write('<html><head>')
1179 self.wfile.write('<title>Denied: %s</title>' % e)
1180 self.wfile.write('</head><body>')
1181 self.wfile.write('auth=%s<p>' % auth)
1182 self.wfile.write('pairs=%s<p>' % pairs)
1183 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1184 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1185 self.wfile.write('</body></html>')
1186 return True
1187
1188 # Authentication successful.
1189 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001190 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001191 self.end_headers()
1192 self.wfile.write('<html><head>')
1193 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1194 self.wfile.write('</head><body>')
1195 self.wfile.write('auth=%s<p>' % auth)
1196 self.wfile.write('pairs=%s<p>' % pairs)
1197 self.wfile.write('</body></html>')
1198
1199 return True
1200
1201 def SlowServerHandler(self):
1202 """Wait for the user suggested time before responding. The syntax is
1203 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001204 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001205 return False
1206 query_char = self.path.find('?')
1207 wait_sec = 1.0
1208 if query_char >= 0:
1209 try:
1210 wait_sec = int(self.path[query_char + 1:])
1211 except ValueError:
1212 pass
1213 time.sleep(wait_sec)
1214 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001215 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001216 self.end_headers()
1217 self.wfile.write("waited %d seconds" % wait_sec)
1218 return True
1219
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001220 def ChunkedServerHandler(self):
1221 """Send chunked response. Allows to specify chunks parameters:
1222 - waitBeforeHeaders - ms to wait before sending headers
1223 - waitBetweenChunks - ms to wait between chunks
1224 - chunkSize - size of each chunk in bytes
1225 - chunksNumber - number of chunks
1226 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1227 waits one second, then sends headers and five chunks five bytes each."""
1228 if not self._ShouldHandleRequest("/chunked"):
1229 return False
1230 query_char = self.path.find('?')
1231 chunkedSettings = {'waitBeforeHeaders' : 0,
1232 'waitBetweenChunks' : 0,
1233 'chunkSize' : 5,
1234 'chunksNumber' : 5}
1235 if query_char >= 0:
1236 params = self.path[query_char + 1:].split('&')
1237 for param in params:
1238 keyValue = param.split('=')
1239 if len(keyValue) == 2:
1240 try:
1241 chunkedSettings[keyValue[0]] = int(keyValue[1])
1242 except ValueError:
1243 pass
1244 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1245 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1246 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001247 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001248 self.send_header('Connection', 'close')
1249 self.send_header('Transfer-Encoding', 'chunked')
1250 self.end_headers()
1251 # Chunked encoding: sending all chunks, then final zero-length chunk and
1252 # then final CRLF.
1253 for i in range(0, chunkedSettings['chunksNumber']):
1254 if i > 0:
1255 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1256 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1257 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1258 self.sendChunkHelp('')
1259 return True
1260
initial.commit94958cf2008-07-26 22:42:52 +00001261 def ContentTypeHandler(self):
1262 """Returns a string of html with the given content type. E.g.,
1263 /contenttype?text/css returns an html file with the Content-Type
1264 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001265 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001266 return False
1267 query_char = self.path.find('?')
1268 content_type = self.path[query_char + 1:].strip()
1269 if not content_type:
1270 content_type = 'text/html'
1271 self.send_response(200)
1272 self.send_header('Content-Type', content_type)
1273 self.end_headers()
1274 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1275 return True
1276
creis@google.com2f4f6a42011-03-25 19:44:19 +00001277 def NoContentHandler(self):
1278 """Returns a 204 No Content response."""
1279 if not self._ShouldHandleRequest("/nocontent"):
1280 return False
1281 self.send_response(204)
1282 self.end_headers()
1283 return True
1284
initial.commit94958cf2008-07-26 22:42:52 +00001285 def ServerRedirectHandler(self):
1286 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001287 '/server-redirect?http://foo.bar/asdf' to redirect to
1288 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001289
1290 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001291 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001292 return False
1293
1294 query_char = self.path.find('?')
1295 if query_char < 0 or len(self.path) <= query_char + 1:
1296 self.sendRedirectHelp(test_name)
1297 return True
1298 dest = self.path[query_char + 1:]
1299
1300 self.send_response(301) # moved permanently
1301 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001302 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001303 self.end_headers()
1304 self.wfile.write('<html><head>')
1305 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1306
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001307 return True
initial.commit94958cf2008-07-26 22:42:52 +00001308
1309 def ClientRedirectHandler(self):
1310 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001311 '/client-redirect?http://foo.bar/asdf' to redirect to
1312 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001313
1314 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001315 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001316 return False
1317
1318 query_char = self.path.find('?');
1319 if query_char < 0 or len(self.path) <= query_char + 1:
1320 self.sendRedirectHelp(test_name)
1321 return True
1322 dest = self.path[query_char + 1:]
1323
1324 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001325 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001326 self.end_headers()
1327 self.wfile.write('<html><head>')
1328 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1329 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1330
1331 return True
1332
tony@chromium.org03266982010-03-05 03:18:42 +00001333 def MultipartHandler(self):
1334 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001335 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001336 if not self._ShouldHandleRequest(test_name):
1337 return False
1338
1339 num_frames = 10
1340 bound = '12345'
1341 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001342 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001343 'multipart/x-mixed-replace;boundary=' + bound)
1344 self.end_headers()
1345
1346 for i in xrange(num_frames):
1347 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001348 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001349 self.wfile.write('<title>page ' + str(i) + '</title>')
1350 self.wfile.write('page ' + str(i))
1351
1352 self.wfile.write('--' + bound + '--')
1353 return True
1354
tony@chromium.org4cb88302011-09-27 22:13:49 +00001355 def MultipartSlowHandler(self):
1356 """Send a multipart response (3 text/html pages) with a slight delay
1357 between each page. This is similar to how some pages show status using
1358 multipart."""
1359 test_name = '/multipart-slow'
1360 if not self._ShouldHandleRequest(test_name):
1361 return False
1362
1363 num_frames = 3
1364 bound = '12345'
1365 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001366 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001367 'multipart/x-mixed-replace;boundary=' + bound)
1368 self.end_headers()
1369
1370 for i in xrange(num_frames):
1371 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001372 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001373 time.sleep(0.25)
1374 if i == 2:
1375 self.wfile.write('<title>PASS</title>')
1376 else:
1377 self.wfile.write('<title>page ' + str(i) + '</title>')
1378 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1379
1380 self.wfile.write('--' + bound + '--')
1381 return True
1382
initial.commit94958cf2008-07-26 22:42:52 +00001383 def DefaultResponseHandler(self):
1384 """This is the catch-all response handler for requests that aren't handled
1385 by one of the special handlers above.
1386 Note that we specify the content-length as without it the https connection
1387 is not closed properly (and the browser keeps expecting data)."""
1388
1389 contents = "Default response given for path: " + self.path
1390 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001391 self.send_header('Content-Type', 'text/html')
1392 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001393 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001394 if (self.command != 'HEAD'):
1395 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001396 return True
1397
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001398 def RedirectConnectHandler(self):
1399 """Sends a redirect to the CONNECT request for www.redirect.com. This
1400 response is not specified by the RFC, so the browser should not follow
1401 the redirect."""
1402
1403 if (self.path.find("www.redirect.com") < 0):
1404 return False
1405
1406 dest = "http://www.destination.com/foo.js"
1407
1408 self.send_response(302) # moved temporarily
1409 self.send_header('Location', dest)
1410 self.send_header('Connection', 'close')
1411 self.end_headers()
1412 return True
1413
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001414 def ServerAuthConnectHandler(self):
1415 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1416 response doesn't make sense because the proxy server cannot request
1417 server authentication."""
1418
1419 if (self.path.find("www.server-auth.com") < 0):
1420 return False
1421
1422 challenge = 'Basic realm="WallyWorld"'
1423
1424 self.send_response(401) # unauthorized
1425 self.send_header('WWW-Authenticate', challenge)
1426 self.send_header('Connection', 'close')
1427 self.end_headers()
1428 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001429
1430 def DefaultConnectResponseHandler(self):
1431 """This is the catch-all response handler for CONNECT requests that aren't
1432 handled by one of the special handlers above. Real Web servers respond
1433 with 400 to CONNECT requests."""
1434
1435 contents = "Your client has issued a malformed or illegal request."
1436 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001437 self.send_header('Content-Type', 'text/html')
1438 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001439 self.end_headers()
1440 self.wfile.write(contents)
1441 return True
1442
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001443 def DeviceManagementHandler(self):
1444 """Delegates to the device management service used for cloud policy."""
1445 if not self._ShouldHandleRequest("/device_management"):
1446 return False
1447
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001448 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001449
1450 if not self.server._device_management_handler:
1451 import device_management
1452 policy_path = os.path.join(self.server.data_dir, 'device_management')
1453 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001454 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001455 self.server.policy_keys,
1456 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001457
1458 http_response, raw_reply = (
1459 self.server._device_management_handler.HandleRequest(self.path,
1460 self.headers,
1461 raw_request))
1462 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001463 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001464 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001465 self.end_headers()
1466 self.wfile.write(raw_reply)
1467 return True
1468
initial.commit94958cf2008-07-26 22:42:52 +00001469 # called by the redirect handling function when there is no parameter
1470 def sendRedirectHelp(self, redirect_name):
1471 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001472 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001473 self.end_headers()
1474 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1475 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1476 self.wfile.write('</body></html>')
1477
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001478 # called by chunked handling function
1479 def sendChunkHelp(self, chunk):
1480 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1481 self.wfile.write('%X\r\n' % len(chunk))
1482 self.wfile.write(chunk)
1483 self.wfile.write('\r\n')
1484
akalin@chromium.org154bb132010-11-12 02:20:27 +00001485
1486class SyncPageHandler(BasePageHandler):
1487 """Handler for the main HTTP sync server."""
1488
1489 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001490 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001491 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001492 self.ChromiumSyncDisableNotificationsOpHandler,
1493 self.ChromiumSyncEnableNotificationsOpHandler,
1494 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001495 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001496 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001497 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001498 self.ChromiumSyncErrorOpHandler,
1499 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001500
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001501 post_handlers = [self.ChromiumSyncCommandHandler,
1502 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001503 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001504 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001505 post_handlers, [])
1506
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001507
akalin@chromium.org154bb132010-11-12 02:20:27 +00001508 def ChromiumSyncTimeHandler(self):
1509 """Handle Chromium sync .../time requests.
1510
1511 The syncer sometimes checks server reachability by examining /time.
1512 """
1513 test_name = "/chromiumsync/time"
1514 if not self._ShouldHandleRequest(test_name):
1515 return False
1516
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001517 # Chrome hates it if we send a response before reading the request.
1518 if self.headers.getheader('content-length'):
1519 length = int(self.headers.getheader('content-length'))
1520 raw_request = self.rfile.read(length)
1521
akalin@chromium.org154bb132010-11-12 02:20:27 +00001522 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001523 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001524 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001525 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001526 return True
1527
1528 def ChromiumSyncCommandHandler(self):
1529 """Handle a chromiumsync command arriving via http.
1530
1531 This covers all sync protocol commands: authentication, getupdates, and
1532 commit.
1533 """
1534 test_name = "/chromiumsync/command"
1535 if not self._ShouldHandleRequest(test_name):
1536 return False
1537
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001538 length = int(self.headers.getheader('content-length'))
1539 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001540 http_response = 200
1541 raw_reply = None
1542 if not self.server.GetAuthenticated():
1543 http_response = 401
1544 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1545 else:
1546 http_response, raw_reply = self.server.HandleCommand(
1547 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001548
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001549 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001550 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001551 if http_response == 401:
1552 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001553 self.end_headers()
1554 self.wfile.write(raw_reply)
1555 return True
1556
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001557 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001558 test_name = "/chromiumsync/migrate"
1559 if not self._ShouldHandleRequest(test_name):
1560 return False
1561
1562 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1563 self.path)
1564 self.send_response(http_response)
1565 self.send_header('Content-Type', 'text/html')
1566 self.send_header('Content-Length', len(raw_reply))
1567 self.end_headers()
1568 self.wfile.write(raw_reply)
1569 return True
1570
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001571 def ChromiumSyncCredHandler(self):
1572 test_name = "/chromiumsync/cred"
1573 if not self._ShouldHandleRequest(test_name):
1574 return False
1575 try:
1576 query = urlparse.urlparse(self.path)[4]
1577 cred_valid = urlparse.parse_qs(query)['valid']
1578 if cred_valid[0] == 'True':
1579 self.server.SetAuthenticated(True)
1580 else:
1581 self.server.SetAuthenticated(False)
1582 except:
1583 self.server.SetAuthenticated(False)
1584
1585 http_response = 200
1586 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1587 self.send_response(http_response)
1588 self.send_header('Content-Type', 'text/html')
1589 self.send_header('Content-Length', len(raw_reply))
1590 self.end_headers()
1591 self.wfile.write(raw_reply)
1592 return True
1593
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001594 def ChromiumSyncDisableNotificationsOpHandler(self):
1595 test_name = "/chromiumsync/disablenotifications"
1596 if not self._ShouldHandleRequest(test_name):
1597 return False
1598 self.server.GetXmppServer().DisableNotifications()
1599 result = 200
1600 raw_reply = ('<html><title>Notifications disabled</title>'
1601 '<H1>Notifications disabled</H1></html>')
1602 self.send_response(result)
1603 self.send_header('Content-Type', 'text/html')
1604 self.send_header('Content-Length', len(raw_reply))
1605 self.end_headers()
1606 self.wfile.write(raw_reply)
1607 return True;
1608
1609 def ChromiumSyncEnableNotificationsOpHandler(self):
1610 test_name = "/chromiumsync/enablenotifications"
1611 if not self._ShouldHandleRequest(test_name):
1612 return False
1613 self.server.GetXmppServer().EnableNotifications()
1614 result = 200
1615 raw_reply = ('<html><title>Notifications enabled</title>'
1616 '<H1>Notifications enabled</H1></html>')
1617 self.send_response(result)
1618 self.send_header('Content-Type', 'text/html')
1619 self.send_header('Content-Length', len(raw_reply))
1620 self.end_headers()
1621 self.wfile.write(raw_reply)
1622 return True;
1623
1624 def ChromiumSyncSendNotificationOpHandler(self):
1625 test_name = "/chromiumsync/sendnotification"
1626 if not self._ShouldHandleRequest(test_name):
1627 return False
1628 query = urlparse.urlparse(self.path)[4]
1629 query_params = urlparse.parse_qs(query)
1630 channel = ''
1631 data = ''
1632 if 'channel' in query_params:
1633 channel = query_params['channel'][0]
1634 if 'data' in query_params:
1635 data = query_params['data'][0]
1636 self.server.GetXmppServer().SendNotification(channel, data)
1637 result = 200
1638 raw_reply = ('<html><title>Notification sent</title>'
1639 '<H1>Notification sent with channel "%s" '
1640 'and data "%s"</H1></html>'
1641 % (channel, data))
1642 self.send_response(result)
1643 self.send_header('Content-Type', 'text/html')
1644 self.send_header('Content-Length', len(raw_reply))
1645 self.end_headers()
1646 self.wfile.write(raw_reply)
1647 return True;
1648
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001649 def ChromiumSyncBirthdayErrorOpHandler(self):
1650 test_name = "/chromiumsync/birthdayerror"
1651 if not self._ShouldHandleRequest(test_name):
1652 return False
1653 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1654 self.send_response(result)
1655 self.send_header('Content-Type', 'text/html')
1656 self.send_header('Content-Length', len(raw_reply))
1657 self.end_headers()
1658 self.wfile.write(raw_reply)
1659 return True;
1660
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001661 def ChromiumSyncTransientErrorOpHandler(self):
1662 test_name = "/chromiumsync/transienterror"
1663 if not self._ShouldHandleRequest(test_name):
1664 return False
1665 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1666 self.send_response(result)
1667 self.send_header('Content-Type', 'text/html')
1668 self.send_header('Content-Length', len(raw_reply))
1669 self.end_headers()
1670 self.wfile.write(raw_reply)
1671 return True;
1672
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001673 def ChromiumSyncErrorOpHandler(self):
1674 test_name = "/chromiumsync/error"
1675 if not self._ShouldHandleRequest(test_name):
1676 return False
1677 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1678 self.path)
1679 self.send_response(result)
1680 self.send_header('Content-Type', 'text/html')
1681 self.send_header('Content-Length', len(raw_reply))
1682 self.end_headers()
1683 self.wfile.write(raw_reply)
1684 return True;
1685
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001686 def ChromiumSyncSyncTabsOpHandler(self):
1687 test_name = "/chromiumsync/synctabs"
1688 if not self._ShouldHandleRequest(test_name):
1689 return False
1690 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1691 self.send_response(result)
1692 self.send_header('Content-Type', 'text/html')
1693 self.send_header('Content-Length', len(raw_reply))
1694 self.end_headers()
1695 self.wfile.write(raw_reply)
1696 return True;
1697
akalin@chromium.org154bb132010-11-12 02:20:27 +00001698
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001699def MakeDataDir():
1700 if options.data_dir:
1701 if not os.path.isdir(options.data_dir):
1702 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1703 return None
1704 my_data_dir = options.data_dir
1705 else:
1706 # Create the default path to our data dir, relative to the exe dir.
1707 my_data_dir = os.path.dirname(sys.argv[0])
1708 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001709 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001710
1711 #TODO(ibrar): Must use Find* funtion defined in google\tools
1712 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1713
1714 return my_data_dir
1715
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001716
1717class TCPEchoHandler(SocketServer.BaseRequestHandler):
1718 """The RequestHandler class for TCP echo server.
1719
1720 It is instantiated once per connection to the server, and overrides the
1721 handle() method to implement communication to the client.
1722 """
1723
1724 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001725 """Handles the request from the client and constructs a response."""
1726
1727 data = self.request.recv(65536).strip()
1728 # Verify the "echo request" message received from the client. Send back
1729 # "echo response" message if "echo request" message is valid.
1730 try:
1731 return_data = echo_message.GetEchoResponseData(data)
1732 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001733 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001734 except ValueError:
1735 return
1736
1737 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001738
1739
1740class UDPEchoHandler(SocketServer.BaseRequestHandler):
1741 """The RequestHandler class for UDP echo server.
1742
1743 It is instantiated once per connection to the server, and overrides the
1744 handle() method to implement communication to the client.
1745 """
1746
1747 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001748 """Handles the request from the client and constructs a response."""
1749
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001750 data = self.request[0].strip()
1751 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001752 # Verify the "echo request" message received from the client. Send back
1753 # "echo response" message if "echo request" message is valid.
1754 try:
1755 return_data = echo_message.GetEchoResponseData(data)
1756 if not return_data:
1757 return
1758 except ValueError:
1759 return
1760 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001761
1762
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001763class FileMultiplexer:
1764 def __init__(self, fd1, fd2) :
1765 self.__fd1 = fd1
1766 self.__fd2 = fd2
1767
1768 def __del__(self) :
1769 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1770 self.__fd1.close()
1771 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1772 self.__fd2.close()
1773
1774 def write(self, text) :
1775 self.__fd1.write(text)
1776 self.__fd2.write(text)
1777
1778 def flush(self) :
1779 self.__fd1.flush()
1780 self.__fd2.flush()
1781
initial.commit94958cf2008-07-26 22:42:52 +00001782def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001783 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001784 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001785 if options.log_to_console:
1786 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1787 else:
1788 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001789
1790 port = options.port
1791
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001792 server_data = {}
1793
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001794 if options.server_type == SERVER_HTTP:
1795 if options.cert:
1796 # let's make sure the cert file exists.
1797 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001798 print 'specified server cert file not found: ' + options.cert + \
1799 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001800 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001801 for ca_cert in options.ssl_client_ca:
1802 if not os.path.isfile(ca_cert):
1803 print 'specified trusted client CA file not found: ' + ca_cert + \
1804 ' exiting...'
1805 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001806 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001807 options.ssl_client_auth, options.ssl_client_ca,
1808 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001809 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001810 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001811 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001812 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001813
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001814 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001815 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001816 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001817 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001818 server.policy_keys = options.policy_keys
1819 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001820 elif options.server_type == SERVER_SYNC:
1821 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1822 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001823 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1824 server_data['port'] = server.server_port
1825 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001826 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001827 # Used for generating the key (randomly) that encodes the "echo request"
1828 # message.
1829 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001830 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1831 print 'Echo TCP server started on port %d...' % server.server_port
1832 server_data['port'] = server.server_port
1833 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001834 # Used for generating the key (randomly) that encodes the "echo request"
1835 # message.
1836 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001837 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1838 print 'Echo UDP server started on port %d...' % server.server_port
1839 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001840 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001841 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001842 my_data_dir = MakeDataDir()
1843
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001844 # Instantiate a dummy authorizer for managing 'virtual' users
1845 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1846
1847 # Define a new user having full r/w permissions and a read-only
1848 # anonymous user
1849 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1850
1851 authorizer.add_anonymous(my_data_dir)
1852
1853 # Instantiate FTP handler class
1854 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1855 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001856
1857 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001858 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1859 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001860
1861 # Instantiate FTP server class and listen to 127.0.0.1:port
1862 address = ('127.0.0.1', port)
1863 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001864 server_data['port'] = server.socket.getsockname()[1]
1865 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001866
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001867 # Notify the parent that we've started. (BaseServer subclasses
1868 # bind their sockets on construction.)
1869 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001870 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001871 server_data_len = len(server_data_json)
1872 print 'sending server_data: %s (%d bytes)' % (
1873 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001874 if sys.platform == 'win32':
1875 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1876 else:
1877 fd = options.startup_pipe
1878 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001879 # First write the data length as an unsigned 4-byte value. This
1880 # is _not_ using network byte ordering since the other end of the
1881 # pipe is on the same machine.
1882 startup_pipe.write(struct.pack('=L', server_data_len))
1883 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001884 startup_pipe.close()
1885
initial.commit94958cf2008-07-26 22:42:52 +00001886 try:
1887 server.serve_forever()
1888 except KeyboardInterrupt:
1889 print 'shutting down server'
1890 server.stop = True
1891
1892if __name__ == '__main__':
1893 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001894 option_parser.add_option("-f", '--ftp', action='store_const',
1895 const=SERVER_FTP, default=SERVER_HTTP,
1896 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001897 help='start up an FTP server.')
1898 option_parser.add_option('', '--sync', action='store_const',
1899 const=SERVER_SYNC, default=SERVER_HTTP,
1900 dest='server_type',
1901 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001902 option_parser.add_option('', '--tcp-echo', action='store_const',
1903 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1904 dest='server_type',
1905 help='start up a tcp echo server.')
1906 option_parser.add_option('', '--udp-echo', action='store_const',
1907 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1908 dest='server_type',
1909 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001910 option_parser.add_option('', '--log-to-console', action='store_const',
1911 const=True, default=False,
1912 dest='log_to_console',
1913 help='Enables or disables sys.stdout logging to '
1914 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001915 option_parser.add_option('', '--port', default='0', type='int',
1916 help='Port used by the server. If unspecified, the '
1917 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001918 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001919 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001920 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001921 help='Specify that https should be used, specify '
1922 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001923 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001924 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1925 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001926 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1927 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001928 'should include the CA named in the subject of '
1929 'the DER-encoded certificate contained in the '
1930 'specified file. This option may appear multiple '
1931 'times, indicating multiple CA names should be '
1932 'sent in the request.')
1933 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1934 help='Specify the bulk encryption algorithm(s)'
1935 'that will be accepted by the SSL server. Valid '
1936 'values are "aes256", "aes128", "3des", "rc4". If '
1937 'omitted, all algorithms will be used. This '
1938 'option may appear multiple times, indicating '
1939 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001940 option_parser.add_option('', '--file-root-url', default='/files/',
1941 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001942 option_parser.add_option('', '--startup-pipe', type='int',
1943 dest='startup_pipe',
1944 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001945 option_parser.add_option('', '--policy-key', action='append',
1946 dest='policy_keys',
1947 help='Specify a path to a PEM-encoded private key '
1948 'to use for policy signing. May be specified '
1949 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001950 'the server. If ther server has multiple keys, it '
1951 'will rotate through them in at each request a '
1952 'round-robin fashion. The server will generate a '
1953 'random key if none is specified on the command '
1954 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001955 option_parser.add_option('', '--policy-user', default='user@example.com',
1956 dest='policy_user',
1957 help='Specify the user name the server should '
1958 'report back to the client as the user owning the '
1959 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001960 options, args = option_parser.parse_args()
1961
1962 sys.exit(main(options, args))