blob: 59975c72a8ac7e2038381a4c613a87ebc870ded4 [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='):
937 # Note this doesn't handle all valid byte range values (i.e. open ended
938 # ones), just enough for what we needed so far.
939 range = range[6:].split('-')
940 start = int(range[0])
941 end = int(range[1])
942
943 self.send_response(206)
944 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
945 str(len(data))
946 self.send_header('Content-Range', content_range)
947 data = data[start: end + 1]
948 else:
949 self.send_response(200)
950
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000951 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000952 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000953 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000954 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000955 self.end_headers()
956
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000957 if (self.command != 'HEAD'):
958 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000959
960 return True
961
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000962 def SetCookieHandler(self):
963 """This handler just sets a cookie, for testing cookie handling."""
964
965 if not self._ShouldHandleRequest("/set-cookie"):
966 return False
967
968 query_char = self.path.find('?')
969 if query_char != -1:
970 cookie_values = self.path[query_char + 1:].split('&')
971 else:
972 cookie_values = ("",)
973 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000974 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000975 for cookie_value in cookie_values:
976 self.send_header('Set-Cookie', '%s' % cookie_value)
977 self.end_headers()
978 for cookie_value in cookie_values:
979 self.wfile.write('%s' % cookie_value)
980 return True
981
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000982 def SetHeaderHandler(self):
983 """This handler sets a response header. Parameters are in the
984 key%3A%20value&key2%3A%20value2 format."""
985
986 if not self._ShouldHandleRequest("/set-header"):
987 return False
988
989 query_char = self.path.find('?')
990 if query_char != -1:
991 headers_values = self.path[query_char + 1:].split('&')
992 else:
993 headers_values = ("",)
994 self.send_response(200)
995 self.send_header('Content-Type', 'text/html')
996 for header_value in headers_values:
997 header_value = urllib.unquote(header_value)
998 (key, value) = header_value.split(': ', 1)
999 self.send_header(key, value)
1000 self.end_headers()
1001 for header_value in headers_values:
1002 self.wfile.write('%s' % header_value)
1003 return True
1004
initial.commit94958cf2008-07-26 22:42:52 +00001005 def AuthBasicHandler(self):
1006 """This handler tests 'Basic' authentication. It just sends a page with
1007 title 'user/pass' if you succeed."""
1008
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001009 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001010 return False
1011
1012 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001013 expected_password = 'secret'
1014 realm = 'testrealm'
1015 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001016
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001017 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1018 query_params = cgi.parse_qs(query, True)
1019 if 'set-cookie-if-challenged' in query_params:
1020 set_cookie_if_challenged = True
1021 if 'password' in query_params:
1022 expected_password = query_params['password'][0]
1023 if 'realm' in query_params:
1024 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001025
initial.commit94958cf2008-07-26 22:42:52 +00001026 auth = self.headers.getheader('authorization')
1027 try:
1028 if not auth:
1029 raise Exception('no auth')
1030 b64str = re.findall(r'Basic (\S+)', auth)[0]
1031 userpass = base64.b64decode(b64str)
1032 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001033 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001034 raise Exception('wrong password')
1035 except Exception, e:
1036 # Authentication failed.
1037 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001038 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001039 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001040 if set_cookie_if_challenged:
1041 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001042 self.end_headers()
1043 self.wfile.write('<html><head>')
1044 self.wfile.write('<title>Denied: %s</title>' % e)
1045 self.wfile.write('</head><body>')
1046 self.wfile.write('auth=%s<p>' % auth)
1047 self.wfile.write('b64str=%s<p>' % b64str)
1048 self.wfile.write('username: %s<p>' % username)
1049 self.wfile.write('userpass: %s<p>' % userpass)
1050 self.wfile.write('password: %s<p>' % password)
1051 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1052 self.wfile.write('</body></html>')
1053 return True
1054
1055 # Authentication successful. (Return a cachable response to allow for
1056 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001057 old_protocol_version = self.protocol_version
1058 self.protocol_version = "HTTP/1.1"
1059
initial.commit94958cf2008-07-26 22:42:52 +00001060 if_none_match = self.headers.getheader('if-none-match')
1061 if if_none_match == "abc":
1062 self.send_response(304)
1063 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001064 elif url_path.endswith(".gif"):
1065 # Using chrome/test/data/google/logo.gif as the test image
1066 test_image_path = ['google', 'logo.gif']
1067 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1068 if not os.path.isfile(gif_path):
1069 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001070 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001071 return True
1072
1073 f = open(gif_path, "rb")
1074 data = f.read()
1075 f.close()
1076
1077 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001078 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001079 self.send_header('Cache-control', 'max-age=60000')
1080 self.send_header('Etag', 'abc')
1081 self.end_headers()
1082 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001083 else:
1084 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001085 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001086 self.send_header('Cache-control', 'max-age=60000')
1087 self.send_header('Etag', 'abc')
1088 self.end_headers()
1089 self.wfile.write('<html><head>')
1090 self.wfile.write('<title>%s/%s</title>' % (username, password))
1091 self.wfile.write('</head><body>')
1092 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001093 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001094 self.wfile.write('</body></html>')
1095
rvargas@google.com54453b72011-05-19 01:11:11 +00001096 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001097 return True
1098
tonyg@chromium.org75054202010-03-31 22:06:10 +00001099 def GetNonce(self, force_reset=False):
1100 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001101
tonyg@chromium.org75054202010-03-31 22:06:10 +00001102 This is a fake implementation. A real implementation would only use a given
1103 nonce a single time (hence the name n-once). However, for the purposes of
1104 unittesting, we don't care about the security of the nonce.
1105
1106 Args:
1107 force_reset: Iff set, the nonce will be changed. Useful for testing the
1108 "stale" response.
1109 """
1110 if force_reset or not self.server.nonce_time:
1111 self.server.nonce_time = time.time()
1112 return _new_md5('privatekey%s%d' %
1113 (self.path, self.server.nonce_time)).hexdigest()
1114
1115 def AuthDigestHandler(self):
1116 """This handler tests 'Digest' authentication.
1117
1118 It just sends a page with title 'user/pass' if you succeed.
1119
1120 A stale response is sent iff "stale" is present in the request path.
1121 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001122 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001123 return False
1124
tonyg@chromium.org75054202010-03-31 22:06:10 +00001125 stale = 'stale' in self.path
1126 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001127 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001128 password = 'secret'
1129 realm = 'testrealm'
1130
1131 auth = self.headers.getheader('authorization')
1132 pairs = {}
1133 try:
1134 if not auth:
1135 raise Exception('no auth')
1136 if not auth.startswith('Digest'):
1137 raise Exception('not digest')
1138 # Pull out all the name="value" pairs as a dictionary.
1139 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1140
1141 # Make sure it's all valid.
1142 if pairs['nonce'] != nonce:
1143 raise Exception('wrong nonce')
1144 if pairs['opaque'] != opaque:
1145 raise Exception('wrong opaque')
1146
1147 # Check the 'response' value and make sure it matches our magic hash.
1148 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001149 hash_a1 = _new_md5(
1150 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001151 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001152 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001153 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001154 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1155 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001156 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001157
1158 if pairs['response'] != response:
1159 raise Exception('wrong password')
1160 except Exception, e:
1161 # Authentication failed.
1162 self.send_response(401)
1163 hdr = ('Digest '
1164 'realm="%s", '
1165 'domain="/", '
1166 'qop="auth", '
1167 'algorithm=MD5, '
1168 'nonce="%s", '
1169 'opaque="%s"') % (realm, nonce, opaque)
1170 if stale:
1171 hdr += ', stale="TRUE"'
1172 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001173 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001174 self.end_headers()
1175 self.wfile.write('<html><head>')
1176 self.wfile.write('<title>Denied: %s</title>' % e)
1177 self.wfile.write('</head><body>')
1178 self.wfile.write('auth=%s<p>' % auth)
1179 self.wfile.write('pairs=%s<p>' % pairs)
1180 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1181 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1182 self.wfile.write('</body></html>')
1183 return True
1184
1185 # Authentication successful.
1186 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001187 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001188 self.end_headers()
1189 self.wfile.write('<html><head>')
1190 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1191 self.wfile.write('</head><body>')
1192 self.wfile.write('auth=%s<p>' % auth)
1193 self.wfile.write('pairs=%s<p>' % pairs)
1194 self.wfile.write('</body></html>')
1195
1196 return True
1197
1198 def SlowServerHandler(self):
1199 """Wait for the user suggested time before responding. The syntax is
1200 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001201 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001202 return False
1203 query_char = self.path.find('?')
1204 wait_sec = 1.0
1205 if query_char >= 0:
1206 try:
1207 wait_sec = int(self.path[query_char + 1:])
1208 except ValueError:
1209 pass
1210 time.sleep(wait_sec)
1211 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001212 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001213 self.end_headers()
1214 self.wfile.write("waited %d seconds" % wait_sec)
1215 return True
1216
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001217 def ChunkedServerHandler(self):
1218 """Send chunked response. Allows to specify chunks parameters:
1219 - waitBeforeHeaders - ms to wait before sending headers
1220 - waitBetweenChunks - ms to wait between chunks
1221 - chunkSize - size of each chunk in bytes
1222 - chunksNumber - number of chunks
1223 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1224 waits one second, then sends headers and five chunks five bytes each."""
1225 if not self._ShouldHandleRequest("/chunked"):
1226 return False
1227 query_char = self.path.find('?')
1228 chunkedSettings = {'waitBeforeHeaders' : 0,
1229 'waitBetweenChunks' : 0,
1230 'chunkSize' : 5,
1231 'chunksNumber' : 5}
1232 if query_char >= 0:
1233 params = self.path[query_char + 1:].split('&')
1234 for param in params:
1235 keyValue = param.split('=')
1236 if len(keyValue) == 2:
1237 try:
1238 chunkedSettings[keyValue[0]] = int(keyValue[1])
1239 except ValueError:
1240 pass
1241 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1242 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1243 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001244 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001245 self.send_header('Connection', 'close')
1246 self.send_header('Transfer-Encoding', 'chunked')
1247 self.end_headers()
1248 # Chunked encoding: sending all chunks, then final zero-length chunk and
1249 # then final CRLF.
1250 for i in range(0, chunkedSettings['chunksNumber']):
1251 if i > 0:
1252 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1253 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1254 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1255 self.sendChunkHelp('')
1256 return True
1257
initial.commit94958cf2008-07-26 22:42:52 +00001258 def ContentTypeHandler(self):
1259 """Returns a string of html with the given content type. E.g.,
1260 /contenttype?text/css returns an html file with the Content-Type
1261 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001262 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001263 return False
1264 query_char = self.path.find('?')
1265 content_type = self.path[query_char + 1:].strip()
1266 if not content_type:
1267 content_type = 'text/html'
1268 self.send_response(200)
1269 self.send_header('Content-Type', content_type)
1270 self.end_headers()
1271 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1272 return True
1273
creis@google.com2f4f6a42011-03-25 19:44:19 +00001274 def NoContentHandler(self):
1275 """Returns a 204 No Content response."""
1276 if not self._ShouldHandleRequest("/nocontent"):
1277 return False
1278 self.send_response(204)
1279 self.end_headers()
1280 return True
1281
initial.commit94958cf2008-07-26 22:42:52 +00001282 def ServerRedirectHandler(self):
1283 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001284 '/server-redirect?http://foo.bar/asdf' to redirect to
1285 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001286
1287 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001288 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001289 return False
1290
1291 query_char = self.path.find('?')
1292 if query_char < 0 or len(self.path) <= query_char + 1:
1293 self.sendRedirectHelp(test_name)
1294 return True
1295 dest = self.path[query_char + 1:]
1296
1297 self.send_response(301) # moved permanently
1298 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001299 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001300 self.end_headers()
1301 self.wfile.write('<html><head>')
1302 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1303
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001304 return True
initial.commit94958cf2008-07-26 22:42:52 +00001305
1306 def ClientRedirectHandler(self):
1307 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001308 '/client-redirect?http://foo.bar/asdf' to redirect to
1309 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001310
1311 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001312 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001313 return False
1314
1315 query_char = self.path.find('?');
1316 if query_char < 0 or len(self.path) <= query_char + 1:
1317 self.sendRedirectHelp(test_name)
1318 return True
1319 dest = self.path[query_char + 1:]
1320
1321 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001322 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001323 self.end_headers()
1324 self.wfile.write('<html><head>')
1325 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1326 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1327
1328 return True
1329
tony@chromium.org03266982010-03-05 03:18:42 +00001330 def MultipartHandler(self):
1331 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001332 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001333 if not self._ShouldHandleRequest(test_name):
1334 return False
1335
1336 num_frames = 10
1337 bound = '12345'
1338 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001339 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001340 'multipart/x-mixed-replace;boundary=' + bound)
1341 self.end_headers()
1342
1343 for i in xrange(num_frames):
1344 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001345 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001346 self.wfile.write('<title>page ' + str(i) + '</title>')
1347 self.wfile.write('page ' + str(i))
1348
1349 self.wfile.write('--' + bound + '--')
1350 return True
1351
tony@chromium.org4cb88302011-09-27 22:13:49 +00001352 def MultipartSlowHandler(self):
1353 """Send a multipart response (3 text/html pages) with a slight delay
1354 between each page. This is similar to how some pages show status using
1355 multipart."""
1356 test_name = '/multipart-slow'
1357 if not self._ShouldHandleRequest(test_name):
1358 return False
1359
1360 num_frames = 3
1361 bound = '12345'
1362 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001363 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001364 'multipart/x-mixed-replace;boundary=' + bound)
1365 self.end_headers()
1366
1367 for i in xrange(num_frames):
1368 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001369 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001370 time.sleep(0.25)
1371 if i == 2:
1372 self.wfile.write('<title>PASS</title>')
1373 else:
1374 self.wfile.write('<title>page ' + str(i) + '</title>')
1375 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1376
1377 self.wfile.write('--' + bound + '--')
1378 return True
1379
initial.commit94958cf2008-07-26 22:42:52 +00001380 def DefaultResponseHandler(self):
1381 """This is the catch-all response handler for requests that aren't handled
1382 by one of the special handlers above.
1383 Note that we specify the content-length as without it the https connection
1384 is not closed properly (and the browser keeps expecting data)."""
1385
1386 contents = "Default response given for path: " + self.path
1387 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001388 self.send_header('Content-Type', 'text/html')
1389 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001390 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001391 if (self.command != 'HEAD'):
1392 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001393 return True
1394
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001395 def RedirectConnectHandler(self):
1396 """Sends a redirect to the CONNECT request for www.redirect.com. This
1397 response is not specified by the RFC, so the browser should not follow
1398 the redirect."""
1399
1400 if (self.path.find("www.redirect.com") < 0):
1401 return False
1402
1403 dest = "http://www.destination.com/foo.js"
1404
1405 self.send_response(302) # moved temporarily
1406 self.send_header('Location', dest)
1407 self.send_header('Connection', 'close')
1408 self.end_headers()
1409 return True
1410
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001411 def ServerAuthConnectHandler(self):
1412 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1413 response doesn't make sense because the proxy server cannot request
1414 server authentication."""
1415
1416 if (self.path.find("www.server-auth.com") < 0):
1417 return False
1418
1419 challenge = 'Basic realm="WallyWorld"'
1420
1421 self.send_response(401) # unauthorized
1422 self.send_header('WWW-Authenticate', challenge)
1423 self.send_header('Connection', 'close')
1424 self.end_headers()
1425 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001426
1427 def DefaultConnectResponseHandler(self):
1428 """This is the catch-all response handler for CONNECT requests that aren't
1429 handled by one of the special handlers above. Real Web servers respond
1430 with 400 to CONNECT requests."""
1431
1432 contents = "Your client has issued a malformed or illegal request."
1433 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001434 self.send_header('Content-Type', 'text/html')
1435 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001436 self.end_headers()
1437 self.wfile.write(contents)
1438 return True
1439
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001440 def DeviceManagementHandler(self):
1441 """Delegates to the device management service used for cloud policy."""
1442 if not self._ShouldHandleRequest("/device_management"):
1443 return False
1444
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001445 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001446
1447 if not self.server._device_management_handler:
1448 import device_management
1449 policy_path = os.path.join(self.server.data_dir, 'device_management')
1450 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001451 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001452 self.server.policy_keys,
1453 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001454
1455 http_response, raw_reply = (
1456 self.server._device_management_handler.HandleRequest(self.path,
1457 self.headers,
1458 raw_request))
1459 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001460 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001461 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001462 self.end_headers()
1463 self.wfile.write(raw_reply)
1464 return True
1465
initial.commit94958cf2008-07-26 22:42:52 +00001466 # called by the redirect handling function when there is no parameter
1467 def sendRedirectHelp(self, redirect_name):
1468 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001469 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001470 self.end_headers()
1471 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1472 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1473 self.wfile.write('</body></html>')
1474
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001475 # called by chunked handling function
1476 def sendChunkHelp(self, chunk):
1477 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1478 self.wfile.write('%X\r\n' % len(chunk))
1479 self.wfile.write(chunk)
1480 self.wfile.write('\r\n')
1481
akalin@chromium.org154bb132010-11-12 02:20:27 +00001482
1483class SyncPageHandler(BasePageHandler):
1484 """Handler for the main HTTP sync server."""
1485
1486 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001487 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001488 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001489 self.ChromiumSyncDisableNotificationsOpHandler,
1490 self.ChromiumSyncEnableNotificationsOpHandler,
1491 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001492 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001493 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001494 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001495 self.ChromiumSyncErrorOpHandler,
1496 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001497
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001498 post_handlers = [self.ChromiumSyncCommandHandler,
1499 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001500 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001501 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001502 post_handlers, [])
1503
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001504
akalin@chromium.org154bb132010-11-12 02:20:27 +00001505 def ChromiumSyncTimeHandler(self):
1506 """Handle Chromium sync .../time requests.
1507
1508 The syncer sometimes checks server reachability by examining /time.
1509 """
1510 test_name = "/chromiumsync/time"
1511 if not self._ShouldHandleRequest(test_name):
1512 return False
1513
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001514 # Chrome hates it if we send a response before reading the request.
1515 if self.headers.getheader('content-length'):
1516 length = int(self.headers.getheader('content-length'))
1517 raw_request = self.rfile.read(length)
1518
akalin@chromium.org154bb132010-11-12 02:20:27 +00001519 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001520 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001521 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001522 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001523 return True
1524
1525 def ChromiumSyncCommandHandler(self):
1526 """Handle a chromiumsync command arriving via http.
1527
1528 This covers all sync protocol commands: authentication, getupdates, and
1529 commit.
1530 """
1531 test_name = "/chromiumsync/command"
1532 if not self._ShouldHandleRequest(test_name):
1533 return False
1534
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001535 length = int(self.headers.getheader('content-length'))
1536 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001537 http_response = 200
1538 raw_reply = None
1539 if not self.server.GetAuthenticated():
1540 http_response = 401
1541 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1542 else:
1543 http_response, raw_reply = self.server.HandleCommand(
1544 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001545
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001546 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001547 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001548 if http_response == 401:
1549 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001550 self.end_headers()
1551 self.wfile.write(raw_reply)
1552 return True
1553
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001554 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001555 test_name = "/chromiumsync/migrate"
1556 if not self._ShouldHandleRequest(test_name):
1557 return False
1558
1559 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1560 self.path)
1561 self.send_response(http_response)
1562 self.send_header('Content-Type', 'text/html')
1563 self.send_header('Content-Length', len(raw_reply))
1564 self.end_headers()
1565 self.wfile.write(raw_reply)
1566 return True
1567
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001568 def ChromiumSyncCredHandler(self):
1569 test_name = "/chromiumsync/cred"
1570 if not self._ShouldHandleRequest(test_name):
1571 return False
1572 try:
1573 query = urlparse.urlparse(self.path)[4]
1574 cred_valid = urlparse.parse_qs(query)['valid']
1575 if cred_valid[0] == 'True':
1576 self.server.SetAuthenticated(True)
1577 else:
1578 self.server.SetAuthenticated(False)
1579 except:
1580 self.server.SetAuthenticated(False)
1581
1582 http_response = 200
1583 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1584 self.send_response(http_response)
1585 self.send_header('Content-Type', 'text/html')
1586 self.send_header('Content-Length', len(raw_reply))
1587 self.end_headers()
1588 self.wfile.write(raw_reply)
1589 return True
1590
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001591 def ChromiumSyncDisableNotificationsOpHandler(self):
1592 test_name = "/chromiumsync/disablenotifications"
1593 if not self._ShouldHandleRequest(test_name):
1594 return False
1595 self.server.GetXmppServer().DisableNotifications()
1596 result = 200
1597 raw_reply = ('<html><title>Notifications disabled</title>'
1598 '<H1>Notifications disabled</H1></html>')
1599 self.send_response(result)
1600 self.send_header('Content-Type', 'text/html')
1601 self.send_header('Content-Length', len(raw_reply))
1602 self.end_headers()
1603 self.wfile.write(raw_reply)
1604 return True;
1605
1606 def ChromiumSyncEnableNotificationsOpHandler(self):
1607 test_name = "/chromiumsync/enablenotifications"
1608 if not self._ShouldHandleRequest(test_name):
1609 return False
1610 self.server.GetXmppServer().EnableNotifications()
1611 result = 200
1612 raw_reply = ('<html><title>Notifications enabled</title>'
1613 '<H1>Notifications enabled</H1></html>')
1614 self.send_response(result)
1615 self.send_header('Content-Type', 'text/html')
1616 self.send_header('Content-Length', len(raw_reply))
1617 self.end_headers()
1618 self.wfile.write(raw_reply)
1619 return True;
1620
1621 def ChromiumSyncSendNotificationOpHandler(self):
1622 test_name = "/chromiumsync/sendnotification"
1623 if not self._ShouldHandleRequest(test_name):
1624 return False
1625 query = urlparse.urlparse(self.path)[4]
1626 query_params = urlparse.parse_qs(query)
1627 channel = ''
1628 data = ''
1629 if 'channel' in query_params:
1630 channel = query_params['channel'][0]
1631 if 'data' in query_params:
1632 data = query_params['data'][0]
1633 self.server.GetXmppServer().SendNotification(channel, data)
1634 result = 200
1635 raw_reply = ('<html><title>Notification sent</title>'
1636 '<H1>Notification sent with channel "%s" '
1637 'and data "%s"</H1></html>'
1638 % (channel, data))
1639 self.send_response(result)
1640 self.send_header('Content-Type', 'text/html')
1641 self.send_header('Content-Length', len(raw_reply))
1642 self.end_headers()
1643 self.wfile.write(raw_reply)
1644 return True;
1645
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001646 def ChromiumSyncBirthdayErrorOpHandler(self):
1647 test_name = "/chromiumsync/birthdayerror"
1648 if not self._ShouldHandleRequest(test_name):
1649 return False
1650 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1651 self.send_response(result)
1652 self.send_header('Content-Type', 'text/html')
1653 self.send_header('Content-Length', len(raw_reply))
1654 self.end_headers()
1655 self.wfile.write(raw_reply)
1656 return True;
1657
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001658 def ChromiumSyncTransientErrorOpHandler(self):
1659 test_name = "/chromiumsync/transienterror"
1660 if not self._ShouldHandleRequest(test_name):
1661 return False
1662 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1663 self.send_response(result)
1664 self.send_header('Content-Type', 'text/html')
1665 self.send_header('Content-Length', len(raw_reply))
1666 self.end_headers()
1667 self.wfile.write(raw_reply)
1668 return True;
1669
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001670 def ChromiumSyncErrorOpHandler(self):
1671 test_name = "/chromiumsync/error"
1672 if not self._ShouldHandleRequest(test_name):
1673 return False
1674 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1675 self.path)
1676 self.send_response(result)
1677 self.send_header('Content-Type', 'text/html')
1678 self.send_header('Content-Length', len(raw_reply))
1679 self.end_headers()
1680 self.wfile.write(raw_reply)
1681 return True;
1682
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001683 def ChromiumSyncSyncTabsOpHandler(self):
1684 test_name = "/chromiumsync/synctabs"
1685 if not self._ShouldHandleRequest(test_name):
1686 return False
1687 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1688 self.send_response(result)
1689 self.send_header('Content-Type', 'text/html')
1690 self.send_header('Content-Length', len(raw_reply))
1691 self.end_headers()
1692 self.wfile.write(raw_reply)
1693 return True;
1694
akalin@chromium.org154bb132010-11-12 02:20:27 +00001695
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001696def MakeDataDir():
1697 if options.data_dir:
1698 if not os.path.isdir(options.data_dir):
1699 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1700 return None
1701 my_data_dir = options.data_dir
1702 else:
1703 # Create the default path to our data dir, relative to the exe dir.
1704 my_data_dir = os.path.dirname(sys.argv[0])
1705 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001706 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001707
1708 #TODO(ibrar): Must use Find* funtion defined in google\tools
1709 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1710
1711 return my_data_dir
1712
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001713
1714class TCPEchoHandler(SocketServer.BaseRequestHandler):
1715 """The RequestHandler class for TCP echo server.
1716
1717 It is instantiated once per connection to the server, and overrides the
1718 handle() method to implement communication to the client.
1719 """
1720
1721 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001722 """Handles the request from the client and constructs a response."""
1723
1724 data = self.request.recv(65536).strip()
1725 # Verify the "echo request" message received from the client. Send back
1726 # "echo response" message if "echo request" message is valid.
1727 try:
1728 return_data = echo_message.GetEchoResponseData(data)
1729 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001730 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001731 except ValueError:
1732 return
1733
1734 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001735
1736
1737class UDPEchoHandler(SocketServer.BaseRequestHandler):
1738 """The RequestHandler class for UDP echo server.
1739
1740 It is instantiated once per connection to the server, and overrides the
1741 handle() method to implement communication to the client.
1742 """
1743
1744 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001745 """Handles the request from the client and constructs a response."""
1746
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001747 data = self.request[0].strip()
1748 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001749 # Verify the "echo request" message received from the client. Send back
1750 # "echo response" message if "echo request" message is valid.
1751 try:
1752 return_data = echo_message.GetEchoResponseData(data)
1753 if not return_data:
1754 return
1755 except ValueError:
1756 return
1757 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001758
1759
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001760class FileMultiplexer:
1761 def __init__(self, fd1, fd2) :
1762 self.__fd1 = fd1
1763 self.__fd2 = fd2
1764
1765 def __del__(self) :
1766 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1767 self.__fd1.close()
1768 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1769 self.__fd2.close()
1770
1771 def write(self, text) :
1772 self.__fd1.write(text)
1773 self.__fd2.write(text)
1774
1775 def flush(self) :
1776 self.__fd1.flush()
1777 self.__fd2.flush()
1778
initial.commit94958cf2008-07-26 22:42:52 +00001779def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001780 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001781 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001782 if options.log_to_console:
1783 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1784 else:
1785 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001786
1787 port = options.port
1788
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001789 server_data = {}
1790
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001791 if options.server_type == SERVER_HTTP:
1792 if options.cert:
1793 # let's make sure the cert file exists.
1794 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001795 print 'specified server cert file not found: ' + options.cert + \
1796 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001797 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001798 for ca_cert in options.ssl_client_ca:
1799 if not os.path.isfile(ca_cert):
1800 print 'specified trusted client CA file not found: ' + ca_cert + \
1801 ' exiting...'
1802 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001803 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001804 options.ssl_client_auth, options.ssl_client_ca,
1805 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001806 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001807 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001808 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001809 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001810
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001811 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001812 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001813 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001814 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001815 server.policy_keys = options.policy_keys
1816 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001817 elif options.server_type == SERVER_SYNC:
1818 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1819 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001820 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1821 server_data['port'] = server.server_port
1822 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001823 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001824 # Used for generating the key (randomly) that encodes the "echo request"
1825 # message.
1826 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001827 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1828 print 'Echo TCP server started on port %d...' % server.server_port
1829 server_data['port'] = server.server_port
1830 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001831 # Used for generating the key (randomly) that encodes the "echo request"
1832 # message.
1833 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001834 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1835 print 'Echo UDP server started on port %d...' % server.server_port
1836 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001837 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001838 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001839 my_data_dir = MakeDataDir()
1840
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001841 # Instantiate a dummy authorizer for managing 'virtual' users
1842 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1843
1844 # Define a new user having full r/w permissions and a read-only
1845 # anonymous user
1846 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1847
1848 authorizer.add_anonymous(my_data_dir)
1849
1850 # Instantiate FTP handler class
1851 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1852 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001853
1854 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001855 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1856 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001857
1858 # Instantiate FTP server class and listen to 127.0.0.1:port
1859 address = ('127.0.0.1', port)
1860 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001861 server_data['port'] = server.socket.getsockname()[1]
1862 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001863
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001864 # Notify the parent that we've started. (BaseServer subclasses
1865 # bind their sockets on construction.)
1866 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001867 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001868 server_data_len = len(server_data_json)
1869 print 'sending server_data: %s (%d bytes)' % (
1870 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001871 if sys.platform == 'win32':
1872 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1873 else:
1874 fd = options.startup_pipe
1875 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001876 # First write the data length as an unsigned 4-byte value. This
1877 # is _not_ using network byte ordering since the other end of the
1878 # pipe is on the same machine.
1879 startup_pipe.write(struct.pack('=L', server_data_len))
1880 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001881 startup_pipe.close()
1882
initial.commit94958cf2008-07-26 22:42:52 +00001883 try:
1884 server.serve_forever()
1885 except KeyboardInterrupt:
1886 print 'shutting down server'
1887 server.stop = True
1888
1889if __name__ == '__main__':
1890 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001891 option_parser.add_option("-f", '--ftp', action='store_const',
1892 const=SERVER_FTP, default=SERVER_HTTP,
1893 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001894 help='start up an FTP server.')
1895 option_parser.add_option('', '--sync', action='store_const',
1896 const=SERVER_SYNC, default=SERVER_HTTP,
1897 dest='server_type',
1898 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001899 option_parser.add_option('', '--tcp-echo', action='store_const',
1900 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1901 dest='server_type',
1902 help='start up a tcp echo server.')
1903 option_parser.add_option('', '--udp-echo', action='store_const',
1904 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1905 dest='server_type',
1906 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001907 option_parser.add_option('', '--log-to-console', action='store_const',
1908 const=True, default=False,
1909 dest='log_to_console',
1910 help='Enables or disables sys.stdout logging to '
1911 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001912 option_parser.add_option('', '--port', default='0', type='int',
1913 help='Port used by the server. If unspecified, the '
1914 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001915 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001916 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001917 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001918 help='Specify that https should be used, specify '
1919 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001920 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001921 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1922 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001923 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1924 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001925 'should include the CA named in the subject of '
1926 'the DER-encoded certificate contained in the '
1927 'specified file. This option may appear multiple '
1928 'times, indicating multiple CA names should be '
1929 'sent in the request.')
1930 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1931 help='Specify the bulk encryption algorithm(s)'
1932 'that will be accepted by the SSL server. Valid '
1933 'values are "aes256", "aes128", "3des", "rc4". If '
1934 'omitted, all algorithms will be used. This '
1935 'option may appear multiple times, indicating '
1936 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001937 option_parser.add_option('', '--file-root-url', default='/files/',
1938 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001939 option_parser.add_option('', '--startup-pipe', type='int',
1940 dest='startup_pipe',
1941 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001942 option_parser.add_option('', '--policy-key', action='append',
1943 dest='policy_keys',
1944 help='Specify a path to a PEM-encoded private key '
1945 'to use for policy signing. May be specified '
1946 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001947 'the server. If ther server has multiple keys, it '
1948 'will rotate through them in at each request a '
1949 'round-robin fashion. The server will generate a '
1950 'random key if none is specified on the command '
1951 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001952 option_parser.add_option('', '--policy-user', default='user@example.com',
1953 dest='policy_user',
1954 help='Specify the user name the server should '
1955 'report back to the client as the user owning the '
1956 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001957 options, args = option_parser.parse_args()
1958
1959 sys.exit(main(options, args))