blob: f9177897cc6e17b64a3fa075498365b96f0a7b81 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00002# Copyright (c) 2012 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
agl@chromium.orgf9e66792011-12-12 22:22:19 +000071class RecordingSSLSessionCache(object):
72 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
73 lookups and inserts in order to test session cache behaviours."""
74
75 def __init__(self):
76 self.log = []
77
78 def __getitem__(self, sessionID):
79 self.log.append(('lookup', sessionID))
80 raise KeyError()
81
82 def __setitem__(self, sessionID, session):
83 self.log.append(('insert', sessionID))
84
initial.commit94958cf2008-07-26 22:42:52 +000085class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
86 """This is a specialization of of BaseHTTPServer to allow it
87 to be exited cleanly (by setting its "stop" member to True)."""
88
89 def serve_forever(self):
90 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000091 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000092 while not self.stop:
93 self.handle_request()
94 self.socket.close()
95
96class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
97 """This is a specialization of StoppableHTTPerver that add https support."""
98
davidben@chromium.org31282a12010-08-07 01:10:02 +000099 def __init__(self, server_address, request_hander_class, cert_path,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000100 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
101 record_resume_info):
initial.commit94958cf2008-07-26 22:42:52 +0000102 s = open(cert_path).read()
103 x509 = tlslite.api.X509()
104 x509.parse(s)
105 self.cert_chain = tlslite.api.X509CertChain([x509])
106 s = open(cert_path).read()
107 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000108 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000109 self.ssl_client_cas = []
110 for ca_file in ssl_client_cas:
111 s = open(ca_file).read()
112 x509 = tlslite.api.X509()
113 x509.parse(s)
114 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000115 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
116 if ssl_bulk_ciphers is not None:
117 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000118
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000119 if record_resume_info:
120 # If record_resume_info is true then we'll replace the session cache with
121 # an object that records the lookups and inserts that it sees.
122 self.session_cache = RecordingSSLSessionCache()
123 else:
124 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000125 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
126
127 def handshake(self, tlsConnection):
128 """Creates the SSL connection."""
129 try:
130 tlsConnection.handshakeServer(certChain=self.cert_chain,
131 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000132 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000133 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000134 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000135 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000136 tlsConnection.ignoreAbruptClose = True
137 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000138 except tlslite.api.TLSAbruptCloseError:
139 # Ignore abrupt close.
140 return True
initial.commit94958cf2008-07-26 22:42:52 +0000141 except tlslite.api.TLSError, error:
142 print "Handshake failure:", str(error)
143 return False
144
akalin@chromium.org154bb132010-11-12 02:20:27 +0000145
146class SyncHTTPServer(StoppableHTTPServer):
147 """An HTTP server that handles sync commands."""
148
149 def __init__(self, server_address, request_handler_class):
150 # We import here to avoid pulling in chromiumsync's dependencies
151 # unless strictly necessary.
152 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000153 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000154 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000155 self._sync_handler = chromiumsync.TestServer()
156 self._xmpp_socket_map = {}
157 self._xmpp_server = xmppserver.XmppServer(
158 self._xmpp_socket_map, ('localhost', 0))
159 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000160 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000161
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000162 def GetXmppServer(self):
163 return self._xmpp_server
164
akalin@chromium.org154bb132010-11-12 02:20:27 +0000165 def HandleCommand(self, query, raw_request):
166 return self._sync_handler.HandleCommand(query, raw_request)
167
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000168 def HandleRequestNoBlock(self):
169 """Handles a single request.
170
171 Copied from SocketServer._handle_request_noblock().
172 """
173 try:
174 request, client_address = self.get_request()
175 except socket.error:
176 return
177 if self.verify_request(request, client_address):
178 try:
179 self.process_request(request, client_address)
180 except:
181 self.handle_error(request, client_address)
182 self.close_request(request)
183
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000184 def SetAuthenticated(self, auth_valid):
185 self.authenticated = auth_valid
186
187 def GetAuthenticated(self):
188 return self.authenticated
189
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000190 def serve_forever(self):
191 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
192 """
193
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000194 def HandleXmppSocket(fd, socket_map, handler):
195 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000196
197 Adapted from asyncore.read() et al.
198 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000199 xmpp_connection = socket_map.get(fd)
200 # This could happen if a previous handler call caused fd to get
201 # removed from socket_map.
202 if xmpp_connection is None:
203 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000204 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000205 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000206 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
207 raise
208 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000209 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000210
211 while True:
212 read_fds = [ self.fileno() ]
213 write_fds = []
214 exceptional_fds = []
215
216 for fd, xmpp_connection in self._xmpp_socket_map.items():
217 is_r = xmpp_connection.readable()
218 is_w = xmpp_connection.writable()
219 if is_r:
220 read_fds.append(fd)
221 if is_w:
222 write_fds.append(fd)
223 if is_r or is_w:
224 exceptional_fds.append(fd)
225
226 try:
227 read_fds, write_fds, exceptional_fds = (
228 select.select(read_fds, write_fds, exceptional_fds))
229 except select.error, err:
230 if err.args[0] != errno.EINTR:
231 raise
232 else:
233 continue
234
235 for fd in read_fds:
236 if fd == self.fileno():
237 self.HandleRequestNoBlock()
238 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000239 HandleXmppSocket(fd, self._xmpp_socket_map,
240 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000241
242 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000243 HandleXmppSocket(fd, self._xmpp_socket_map,
244 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000245
246 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000247 HandleXmppSocket(fd, self._xmpp_socket_map,
248 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000249
akalin@chromium.org154bb132010-11-12 02:20:27 +0000250
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000251class TCPEchoServer(SocketServer.TCPServer):
252 """A TCP echo server that echoes back what it has received."""
253
254 def server_bind(self):
255 """Override server_bind to store the server name."""
256 SocketServer.TCPServer.server_bind(self)
257 host, port = self.socket.getsockname()[:2]
258 self.server_name = socket.getfqdn(host)
259 self.server_port = port
260
261 def serve_forever(self):
262 self.stop = False
263 self.nonce_time = None
264 while not self.stop:
265 self.handle_request()
266 self.socket.close()
267
268
269class UDPEchoServer(SocketServer.UDPServer):
270 """A UDP echo server that echoes back what it has received."""
271
272 def server_bind(self):
273 """Override server_bind to store the server name."""
274 SocketServer.UDPServer.server_bind(self)
275 host, port = self.socket.getsockname()[:2]
276 self.server_name = socket.getfqdn(host)
277 self.server_port = port
278
279 def serve_forever(self):
280 self.stop = False
281 self.nonce_time = None
282 while not self.stop:
283 self.handle_request()
284 self.socket.close()
285
286
akalin@chromium.org154bb132010-11-12 02:20:27 +0000287class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
288
289 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000290 connect_handlers, get_handlers, head_handlers, post_handlers,
291 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000292 self._connect_handlers = connect_handlers
293 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000294 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000295 self._post_handlers = post_handlers
296 self._put_handlers = put_handlers
297 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
298 self, request, client_address, socket_server)
299
300 def log_request(self, *args, **kwargs):
301 # Disable request logging to declutter test log output.
302 pass
303
304 def _ShouldHandleRequest(self, handler_name):
305 """Determines if the path can be handled by the handler.
306
307 We consider a handler valid if the path begins with the
308 handler name. It can optionally be followed by "?*", "/*".
309 """
310
311 pattern = re.compile('%s($|\?|/).*' % handler_name)
312 return pattern.match(self.path)
313
314 def do_CONNECT(self):
315 for handler in self._connect_handlers:
316 if handler():
317 return
318
319 def do_GET(self):
320 for handler in self._get_handlers:
321 if handler():
322 return
323
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000324 def do_HEAD(self):
325 for handler in self._head_handlers:
326 if handler():
327 return
328
akalin@chromium.org154bb132010-11-12 02:20:27 +0000329 def do_POST(self):
330 for handler in self._post_handlers:
331 if handler():
332 return
333
334 def do_PUT(self):
335 for handler in self._put_handlers:
336 if handler():
337 return
338
339
340class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000341
342 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000343 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000344 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000345 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000346 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000347 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000348 self.NoCacheMaxAgeTimeHandler,
349 self.NoCacheTimeHandler,
350 self.CacheTimeHandler,
351 self.CacheExpiresHandler,
352 self.CacheProxyRevalidateHandler,
353 self.CachePrivateHandler,
354 self.CachePublicHandler,
355 self.CacheSMaxAgeHandler,
356 self.CacheMustRevalidateHandler,
357 self.CacheMustRevalidateMaxAgeHandler,
358 self.CacheNoStoreHandler,
359 self.CacheNoStoreMaxAgeHandler,
360 self.CacheNoTransformHandler,
361 self.DownloadHandler,
362 self.DownloadFinishHandler,
363 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000364 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000365 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000366 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000367 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000368 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000369 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000370 self.AuthBasicHandler,
371 self.AuthDigestHandler,
372 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000373 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000374 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000375 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000376 self.ServerRedirectHandler,
377 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000378 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000379 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000380 self.GetSSLSessionCacheHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000381 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000382 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000383 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000384 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000385 self.DeviceManagementHandler,
386 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000387 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000388 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000389 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000390 head_handlers = [
391 self.FileHandler,
392 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000393
maruel@google.come250a9b2009-03-10 17:39:46 +0000394 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000395 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000396 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000397 'gif': 'image/gif',
398 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000399 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000400 'pdf' : 'application/pdf',
401 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000402 }
initial.commit94958cf2008-07-26 22:42:52 +0000403 self._default_mime_type = 'text/html'
404
akalin@chromium.org154bb132010-11-12 02:20:27 +0000405 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000406 connect_handlers, get_handlers, head_handlers,
407 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000408
initial.commit94958cf2008-07-26 22:42:52 +0000409 def GetMIMETypeFromName(self, file_name):
410 """Returns the mime type for the specified file_name. So far it only looks
411 at the file extension."""
412
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000413 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000414 if len(extension) == 0:
415 # no extension.
416 return self._default_mime_type
417
ericroman@google.comc17ca532009-05-07 03:51:05 +0000418 # extension starts with a dot, so we need to remove it
419 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000420
initial.commit94958cf2008-07-26 22:42:52 +0000421 def NoCacheMaxAgeTimeHandler(self):
422 """This request handler yields a page with the title set to the current
423 system time, and no caching requested."""
424
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000425 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000426 return False
427
428 self.send_response(200)
429 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000430 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000431 self.end_headers()
432
maruel@google.come250a9b2009-03-10 17:39:46 +0000433 self.wfile.write('<html><head><title>%s</title></head></html>' %
434 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000435
436 return True
437
438 def NoCacheTimeHandler(self):
439 """This request handler yields a page with the title set to the current
440 system time, and no caching requested."""
441
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000442 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000443 return False
444
445 self.send_response(200)
446 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000447 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.end_headers()
449
maruel@google.come250a9b2009-03-10 17:39:46 +0000450 self.wfile.write('<html><head><title>%s</title></head></html>' %
451 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000452
453 return True
454
455 def CacheTimeHandler(self):
456 """This request handler yields a page with the title set to the current
457 system time, and allows caching for one minute."""
458
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000459 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000460 return False
461
462 self.send_response(200)
463 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000464 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000465 self.end_headers()
466
maruel@google.come250a9b2009-03-10 17:39:46 +0000467 self.wfile.write('<html><head><title>%s</title></head></html>' %
468 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000469
470 return True
471
472 def CacheExpiresHandler(self):
473 """This request handler yields a page with the title set to the current
474 system time, and set the page to expire on 1 Jan 2099."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
480 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000481 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000482 self.end_headers()
483
maruel@google.come250a9b2009-03-10 17:39:46 +0000484 self.wfile.write('<html><head><title>%s</title></head></html>' %
485 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000486
487 return True
488
489 def CacheProxyRevalidateHandler(self):
490 """This request handler yields a page with the title set to the current
491 system time, and allows caching for 60 seconds"""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000497 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000498 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
499 self.end_headers()
500
maruel@google.come250a9b2009-03-10 17:39:46 +0000501 self.wfile.write('<html><head><title>%s</title></head></html>' %
502 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000503
504 return True
505
506 def CachePrivateHandler(self):
507 """This request handler yields a page with the title set to the current
508 system time, and allows caching for 5 seconds."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000514 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000515 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000516 self.end_headers()
517
maruel@google.come250a9b2009-03-10 17:39:46 +0000518 self.wfile.write('<html><head><title>%s</title></head></html>' %
519 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000520
521 return True
522
523 def CachePublicHandler(self):
524 """This request handler yields a page with the title set to the current
525 system time, and allows caching for 5 seconds."""
526
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000527 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000528 return False
529
530 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000531 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000532 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000533 self.end_headers()
534
maruel@google.come250a9b2009-03-10 17:39:46 +0000535 self.wfile.write('<html><head><title>%s</title></head></html>' %
536 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000537
538 return True
539
540 def CacheSMaxAgeHandler(self):
541 """This request handler yields a page with the title set to the current
542 system time, and does not allow for caching."""
543
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000544 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000545 return False
546
547 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000548 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000549 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
550 self.end_headers()
551
maruel@google.come250a9b2009-03-10 17:39:46 +0000552 self.wfile.write('<html><head><title>%s</title></head></html>' %
553 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000554
555 return True
556
557 def CacheMustRevalidateHandler(self):
558 """This request handler yields a page with the title set to the current
559 system time, and does not allow caching."""
560
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000561 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000562 return False
563
564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000565 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000566 self.send_header('Cache-Control', 'must-revalidate')
567 self.end_headers()
568
maruel@google.come250a9b2009-03-10 17:39:46 +0000569 self.wfile.write('<html><head><title>%s</title></head></html>' %
570 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000571
572 return True
573
574 def CacheMustRevalidateMaxAgeHandler(self):
575 """This request handler yields a page with the title set to the current
576 system time, and does not allow caching event though max-age of 60
577 seconds is specified."""
578
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000579 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000580 return False
581
582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000583 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000584 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
585 self.end_headers()
586
maruel@google.come250a9b2009-03-10 17:39:46 +0000587 self.wfile.write('<html><head><title>%s</title></head></html>' %
588 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000589
590 return True
591
initial.commit94958cf2008-07-26 22:42:52 +0000592 def CacheNoStoreHandler(self):
593 """This request handler yields a page with the title set to the current
594 system time, and does not allow the page to be stored."""
595
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000596 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000597 return False
598
599 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000600 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000601 self.send_header('Cache-Control', 'no-store')
602 self.end_headers()
603
maruel@google.come250a9b2009-03-10 17:39:46 +0000604 self.wfile.write('<html><head><title>%s</title></head></html>' %
605 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000606
607 return True
608
609 def CacheNoStoreMaxAgeHandler(self):
610 """This request handler yields a page with the title set to the current
611 system time, and does not allow the page to be stored even though max-age
612 of 60 seconds is specified."""
613
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000614 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000615 return False
616
617 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000618 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000619 self.send_header('Cache-Control', 'max-age=60, no-store')
620 self.end_headers()
621
maruel@google.come250a9b2009-03-10 17:39:46 +0000622 self.wfile.write('<html><head><title>%s</title></head></html>' %
623 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000624
625 return True
626
627
628 def CacheNoTransformHandler(self):
629 """This request handler yields a page with the title set to the current
630 system time, and does not allow the content to transformed during
631 user-agent caching"""
632
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000633 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000634 return False
635
636 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000637 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000638 self.send_header('Cache-Control', 'no-transform')
639 self.end_headers()
640
maruel@google.come250a9b2009-03-10 17:39:46 +0000641 self.wfile.write('<html><head><title>%s</title></head></html>' %
642 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000643
644 return True
645
646 def EchoHeader(self):
647 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000648 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000649
ananta@chromium.org56812d02011-04-07 17:52:05 +0000650 """This function echoes back the value of a specific request header"""
651 """while allowing caching for 16 hours."""
652 def EchoHeaderCache(self):
653 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000654
655 def EchoHeaderHelper(self, echo_header):
656 """This function echoes back the value of the request header passed in."""
657 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000658 return False
659
660 query_char = self.path.find('?')
661 if query_char != -1:
662 header_name = self.path[query_char+1:]
663
664 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000665 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000666 if echo_header == '/echoheadercache':
667 self.send_header('Cache-control', 'max-age=60000')
668 else:
669 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000670 # insert a vary header to properly indicate that the cachability of this
671 # request is subject to value of the request header being echoed.
672 if len(header_name) > 0:
673 self.send_header('Vary', header_name)
674 self.end_headers()
675
676 if len(header_name) > 0:
677 self.wfile.write(self.headers.getheader(header_name))
678
679 return True
680
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000681 def ReadRequestBody(self):
682 """This function reads the body of the current HTTP request, handling
683 both plain and chunked transfer encoded requests."""
684
685 if self.headers.getheader('transfer-encoding') != 'chunked':
686 length = int(self.headers.getheader('content-length'))
687 return self.rfile.read(length)
688
689 # Read the request body as chunks.
690 body = ""
691 while True:
692 line = self.rfile.readline()
693 length = int(line, 16)
694 if length == 0:
695 self.rfile.readline()
696 break
697 body += self.rfile.read(length)
698 self.rfile.read(2)
699 return body
700
initial.commit94958cf2008-07-26 22:42:52 +0000701 def EchoHandler(self):
702 """This handler just echoes back the payload of the request, for testing
703 form submission."""
704
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000705 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000706 return False
707
708 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000709 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000710 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000711 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000712 return True
713
714 def EchoTitleHandler(self):
715 """This handler is like Echo, but sets the page title to the request."""
716
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000717 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000718 return False
719
720 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000721 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000722 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000723 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000724 self.wfile.write('<html><head><title>')
725 self.wfile.write(request)
726 self.wfile.write('</title></head></html>')
727 return True
728
729 def EchoAllHandler(self):
730 """This handler yields a (more) human-readable page listing information
731 about the request header & contents."""
732
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000733 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000734 return False
735
736 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000737 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000738 self.end_headers()
739 self.wfile.write('<html><head><style>'
740 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
741 '</style></head><body>'
742 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000743 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000744 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000745
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000746 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000747 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000748 params = cgi.parse_qs(qs, keep_blank_values=1)
749
750 for param in params:
751 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000752
753 self.wfile.write('</pre>')
754
755 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
756
757 self.wfile.write('</body></html>')
758 return True
759
760 def DownloadHandler(self):
761 """This handler sends a downloadable file with or without reporting
762 the size (6K)."""
763
764 if self.path.startswith("/download-unknown-size"):
765 send_length = False
766 elif self.path.startswith("/download-known-size"):
767 send_length = True
768 else:
769 return False
770
771 #
772 # The test which uses this functionality is attempting to send
773 # small chunks of data to the client. Use a fairly large buffer
774 # so that we'll fill chrome's IO buffer enough to force it to
775 # actually write the data.
776 # See also the comments in the client-side of this test in
777 # download_uitest.cc
778 #
779 size_chunk1 = 35*1024
780 size_chunk2 = 10*1024
781
782 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000783 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000784 self.send_header('Cache-Control', 'max-age=0')
785 if send_length:
786 self.send_header('Content-Length', size_chunk1 + size_chunk2)
787 self.end_headers()
788
789 # First chunk of data:
790 self.wfile.write("*" * size_chunk1)
791 self.wfile.flush()
792
793 # handle requests until one of them clears this flag.
794 self.server.waitForDownload = True
795 while self.server.waitForDownload:
796 self.server.handle_request()
797
798 # Second chunk of data:
799 self.wfile.write("*" * size_chunk2)
800 return True
801
802 def DownloadFinishHandler(self):
803 """This handler just tells the server to finish the current download."""
804
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000805 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000806 return False
807
808 self.server.waitForDownload = False
809 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000810 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000811 self.send_header('Cache-Control', 'max-age=0')
812 self.end_headers()
813 return True
814
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000815 def _ReplaceFileData(self, data, query_parameters):
816 """Replaces matching substrings in a file.
817
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000818 If the 'replace_text' URL query parameter is present, it is expected to be
819 of the form old_text:new_text, which indicates that any old_text strings in
820 the file are replaced with new_text. Multiple 'replace_text' parameters may
821 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000822
823 If the parameters are not present, |data| is returned.
824 """
825 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000826 replace_text_values = query_dict.get('replace_text', [])
827 for replace_text_value in replace_text_values:
828 replace_text_args = replace_text_value.split(':')
829 if len(replace_text_args) != 2:
830 raise ValueError(
831 'replace_text must be of form old_text:new_text. Actual value: %s' %
832 replace_text_value)
833 old_text_b64, new_text_b64 = replace_text_args
834 old_text = base64.urlsafe_b64decode(old_text_b64)
835 new_text = base64.urlsafe_b64decode(new_text_b64)
836 data = data.replace(old_text, new_text)
837 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000838
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000839 def ZipFileHandler(self):
840 """This handler sends the contents of the requested file in compressed form.
841 Can pass in a parameter that specifies that the content length be
842 C - the compressed size (OK),
843 U - the uncompressed size (Non-standard, but handled),
844 S - less than compressed (OK because we keep going),
845 M - larger than compressed but less than uncompressed (an error),
846 L - larger than uncompressed (an error)
847 Example: compressedfiles/Picture_1.doc?C
848 """
849
850 prefix = "/compressedfiles/"
851 if not self.path.startswith(prefix):
852 return False
853
854 # Consume a request body if present.
855 if self.command == 'POST' or self.command == 'PUT' :
856 self.ReadRequestBody()
857
858 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
859
860 if not query in ('C', 'U', 'S', 'M', 'L'):
861 return False
862
863 sub_path = url_path[len(prefix):]
864 entries = sub_path.split('/')
865 file_path = os.path.join(self.server.data_dir, *entries)
866 if os.path.isdir(file_path):
867 file_path = os.path.join(file_path, 'index.html')
868
869 if not os.path.isfile(file_path):
870 print "File not found " + sub_path + " full path:" + file_path
871 self.send_error(404)
872 return True
873
874 f = open(file_path, "rb")
875 data = f.read()
876 uncompressed_len = len(data)
877 f.close()
878
879 # Compress the data.
880 data = zlib.compress(data)
881 compressed_len = len(data)
882
883 content_length = compressed_len
884 if query == 'U':
885 content_length = uncompressed_len
886 elif query == 'S':
887 content_length = compressed_len / 2
888 elif query == 'M':
889 content_length = (compressed_len + uncompressed_len) / 2
890 elif query == 'L':
891 content_length = compressed_len + uncompressed_len
892
893 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000894 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000895 self.send_header('Content-encoding', 'deflate')
896 self.send_header('Connection', 'close')
897 self.send_header('Content-Length', content_length)
898 self.send_header('ETag', '\'' + file_path + '\'')
899 self.end_headers()
900
901 self.wfile.write(data)
902
903 return True
904
initial.commit94958cf2008-07-26 22:42:52 +0000905 def FileHandler(self):
906 """This handler sends the contents of the requested file. Wow, it's like
907 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000908 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000909 if not self.path.startswith(prefix):
910 return False
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000911 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000912 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000913 self.ReadRequestBody()
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000914 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000915
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000916 def PostOnlyFileHandler(self):
917 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000918 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000919 if not self.path.startswith(prefix):
920 return False
921 self.ReadRequestBody()
922 return self._FileHandlerHelper(prefix)
923
924 def _FileHandlerHelper(self, prefix):
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000925 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
926 sub_path = url_path[len(prefix):]
927 entries = sub_path.split('/')
928 file_path = os.path.join(self.server.data_dir, *entries)
929 if os.path.isdir(file_path):
930 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000931
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000932 if not os.path.isfile(file_path):
933 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000934 self.send_error(404)
935 return True
936
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000937 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000938 data = f.read()
939 f.close()
940
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000941 data = self._ReplaceFileData(data, query)
942
initial.commit94958cf2008-07-26 22:42:52 +0000943 # If file.mock-http-headers exists, it contains the headers we
944 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000945 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000946 if os.path.isfile(headers_path):
947 f = open(headers_path, "r")
948
949 # "HTTP/1.1 200 OK"
950 response = f.readline()
simonjam@chromium.org3f12f0d2012-02-11 01:38:42 +0000951 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
initial.commit94958cf2008-07-26 22:42:52 +0000952 self.send_response(int(status_code))
953
954 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000955 header_values = re.findall('(\S+):\s*(.*)', line)
956 if len(header_values) > 0:
957 # "name: value"
958 name, value = header_values[0]
959 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000960 f.close()
961 else:
962 # Could be more generic once we support mime-type sniffing, but for
963 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000964
965 range = self.headers.get('Range')
966 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +0000967 # Note this doesn't handle all valid byte range values (i.e. left
968 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +0000969 range = range[6:].split('-')
970 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000971 if range[1]:
972 end = int(range[1])
973 else:
974 end = len(data)
jam@chromium.org41550782010-11-17 23:47:50 +0000975
976 self.send_response(206)
977 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
978 str(len(data))
979 self.send_header('Content-Range', content_range)
980 data = data[start: end + 1]
981 else:
982 self.send_response(200)
983
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000984 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000985 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000986 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000987 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000988 self.end_headers()
989
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000990 if (self.command != 'HEAD'):
991 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000992
993 return True
994
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000995 def SetCookieHandler(self):
996 """This handler just sets a cookie, for testing cookie handling."""
997
998 if not self._ShouldHandleRequest("/set-cookie"):
999 return False
1000
1001 query_char = self.path.find('?')
1002 if query_char != -1:
1003 cookie_values = self.path[query_char + 1:].split('&')
1004 else:
1005 cookie_values = ("",)
1006 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001007 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001008 for cookie_value in cookie_values:
1009 self.send_header('Set-Cookie', '%s' % cookie_value)
1010 self.end_headers()
1011 for cookie_value in cookie_values:
1012 self.wfile.write('%s' % cookie_value)
1013 return True
1014
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001015 def SetHeaderHandler(self):
1016 """This handler sets a response header. Parameters are in the
1017 key%3A%20value&key2%3A%20value2 format."""
1018
1019 if not self._ShouldHandleRequest("/set-header"):
1020 return False
1021
1022 query_char = self.path.find('?')
1023 if query_char != -1:
1024 headers_values = self.path[query_char + 1:].split('&')
1025 else:
1026 headers_values = ("",)
1027 self.send_response(200)
1028 self.send_header('Content-Type', 'text/html')
1029 for header_value in headers_values:
1030 header_value = urllib.unquote(header_value)
1031 (key, value) = header_value.split(': ', 1)
1032 self.send_header(key, value)
1033 self.end_headers()
1034 for header_value in headers_values:
1035 self.wfile.write('%s' % header_value)
1036 return True
1037
initial.commit94958cf2008-07-26 22:42:52 +00001038 def AuthBasicHandler(self):
1039 """This handler tests 'Basic' authentication. It just sends a page with
1040 title 'user/pass' if you succeed."""
1041
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001042 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001043 return False
1044
1045 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001046 expected_password = 'secret'
1047 realm = 'testrealm'
1048 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001049
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001050 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1051 query_params = cgi.parse_qs(query, True)
1052 if 'set-cookie-if-challenged' in query_params:
1053 set_cookie_if_challenged = True
1054 if 'password' in query_params:
1055 expected_password = query_params['password'][0]
1056 if 'realm' in query_params:
1057 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001058
initial.commit94958cf2008-07-26 22:42:52 +00001059 auth = self.headers.getheader('authorization')
1060 try:
1061 if not auth:
1062 raise Exception('no auth')
1063 b64str = re.findall(r'Basic (\S+)', auth)[0]
1064 userpass = base64.b64decode(b64str)
1065 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001066 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001067 raise Exception('wrong password')
1068 except Exception, e:
1069 # Authentication failed.
1070 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001071 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001072 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001073 if set_cookie_if_challenged:
1074 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001075 self.end_headers()
1076 self.wfile.write('<html><head>')
1077 self.wfile.write('<title>Denied: %s</title>' % e)
1078 self.wfile.write('</head><body>')
1079 self.wfile.write('auth=%s<p>' % auth)
1080 self.wfile.write('b64str=%s<p>' % b64str)
1081 self.wfile.write('username: %s<p>' % username)
1082 self.wfile.write('userpass: %s<p>' % userpass)
1083 self.wfile.write('password: %s<p>' % password)
1084 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1085 self.wfile.write('</body></html>')
1086 return True
1087
1088 # Authentication successful. (Return a cachable response to allow for
1089 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001090 old_protocol_version = self.protocol_version
1091 self.protocol_version = "HTTP/1.1"
1092
initial.commit94958cf2008-07-26 22:42:52 +00001093 if_none_match = self.headers.getheader('if-none-match')
1094 if if_none_match == "abc":
1095 self.send_response(304)
1096 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001097 elif url_path.endswith(".gif"):
1098 # Using chrome/test/data/google/logo.gif as the test image
1099 test_image_path = ['google', 'logo.gif']
1100 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1101 if not os.path.isfile(gif_path):
1102 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001103 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001104 return True
1105
1106 f = open(gif_path, "rb")
1107 data = f.read()
1108 f.close()
1109
1110 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001111 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001112 self.send_header('Cache-control', 'max-age=60000')
1113 self.send_header('Etag', 'abc')
1114 self.end_headers()
1115 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001116 else:
1117 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001118 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001119 self.send_header('Cache-control', 'max-age=60000')
1120 self.send_header('Etag', 'abc')
1121 self.end_headers()
1122 self.wfile.write('<html><head>')
1123 self.wfile.write('<title>%s/%s</title>' % (username, password))
1124 self.wfile.write('</head><body>')
1125 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001126 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001127 self.wfile.write('</body></html>')
1128
rvargas@google.com54453b72011-05-19 01:11:11 +00001129 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001130 return True
1131
tonyg@chromium.org75054202010-03-31 22:06:10 +00001132 def GetNonce(self, force_reset=False):
1133 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001134
tonyg@chromium.org75054202010-03-31 22:06:10 +00001135 This is a fake implementation. A real implementation would only use a given
1136 nonce a single time (hence the name n-once). However, for the purposes of
1137 unittesting, we don't care about the security of the nonce.
1138
1139 Args:
1140 force_reset: Iff set, the nonce will be changed. Useful for testing the
1141 "stale" response.
1142 """
1143 if force_reset or not self.server.nonce_time:
1144 self.server.nonce_time = time.time()
1145 return _new_md5('privatekey%s%d' %
1146 (self.path, self.server.nonce_time)).hexdigest()
1147
1148 def AuthDigestHandler(self):
1149 """This handler tests 'Digest' authentication.
1150
1151 It just sends a page with title 'user/pass' if you succeed.
1152
1153 A stale response is sent iff "stale" is present in the request path.
1154 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001155 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001156 return False
1157
tonyg@chromium.org75054202010-03-31 22:06:10 +00001158 stale = 'stale' in self.path
1159 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001160 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001161 password = 'secret'
1162 realm = 'testrealm'
1163
1164 auth = self.headers.getheader('authorization')
1165 pairs = {}
1166 try:
1167 if not auth:
1168 raise Exception('no auth')
1169 if not auth.startswith('Digest'):
1170 raise Exception('not digest')
1171 # Pull out all the name="value" pairs as a dictionary.
1172 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1173
1174 # Make sure it's all valid.
1175 if pairs['nonce'] != nonce:
1176 raise Exception('wrong nonce')
1177 if pairs['opaque'] != opaque:
1178 raise Exception('wrong opaque')
1179
1180 # Check the 'response' value and make sure it matches our magic hash.
1181 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001182 hash_a1 = _new_md5(
1183 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001184 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001185 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001186 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001187 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1188 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001189 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001190
1191 if pairs['response'] != response:
1192 raise Exception('wrong password')
1193 except Exception, e:
1194 # Authentication failed.
1195 self.send_response(401)
1196 hdr = ('Digest '
1197 'realm="%s", '
1198 'domain="/", '
1199 'qop="auth", '
1200 'algorithm=MD5, '
1201 'nonce="%s", '
1202 'opaque="%s"') % (realm, nonce, opaque)
1203 if stale:
1204 hdr += ', stale="TRUE"'
1205 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001206 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001207 self.end_headers()
1208 self.wfile.write('<html><head>')
1209 self.wfile.write('<title>Denied: %s</title>' % e)
1210 self.wfile.write('</head><body>')
1211 self.wfile.write('auth=%s<p>' % auth)
1212 self.wfile.write('pairs=%s<p>' % pairs)
1213 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1214 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1215 self.wfile.write('</body></html>')
1216 return True
1217
1218 # Authentication successful.
1219 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001220 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001221 self.end_headers()
1222 self.wfile.write('<html><head>')
1223 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1224 self.wfile.write('</head><body>')
1225 self.wfile.write('auth=%s<p>' % auth)
1226 self.wfile.write('pairs=%s<p>' % pairs)
1227 self.wfile.write('</body></html>')
1228
1229 return True
1230
1231 def SlowServerHandler(self):
1232 """Wait for the user suggested time before responding. The syntax is
1233 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001234 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001235 return False
1236 query_char = self.path.find('?')
1237 wait_sec = 1.0
1238 if query_char >= 0:
1239 try:
1240 wait_sec = int(self.path[query_char + 1:])
1241 except ValueError:
1242 pass
1243 time.sleep(wait_sec)
1244 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001245 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001246 self.end_headers()
1247 self.wfile.write("waited %d seconds" % wait_sec)
1248 return True
1249
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001250 def ChunkedServerHandler(self):
1251 """Send chunked response. Allows to specify chunks parameters:
1252 - waitBeforeHeaders - ms to wait before sending headers
1253 - waitBetweenChunks - ms to wait between chunks
1254 - chunkSize - size of each chunk in bytes
1255 - chunksNumber - number of chunks
1256 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1257 waits one second, then sends headers and five chunks five bytes each."""
1258 if not self._ShouldHandleRequest("/chunked"):
1259 return False
1260 query_char = self.path.find('?')
1261 chunkedSettings = {'waitBeforeHeaders' : 0,
1262 'waitBetweenChunks' : 0,
1263 'chunkSize' : 5,
1264 'chunksNumber' : 5}
1265 if query_char >= 0:
1266 params = self.path[query_char + 1:].split('&')
1267 for param in params:
1268 keyValue = param.split('=')
1269 if len(keyValue) == 2:
1270 try:
1271 chunkedSettings[keyValue[0]] = int(keyValue[1])
1272 except ValueError:
1273 pass
1274 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1275 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1276 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001277 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001278 self.send_header('Connection', 'close')
1279 self.send_header('Transfer-Encoding', 'chunked')
1280 self.end_headers()
1281 # Chunked encoding: sending all chunks, then final zero-length chunk and
1282 # then final CRLF.
1283 for i in range(0, chunkedSettings['chunksNumber']):
1284 if i > 0:
1285 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1286 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1287 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1288 self.sendChunkHelp('')
1289 return True
1290
initial.commit94958cf2008-07-26 22:42:52 +00001291 def ContentTypeHandler(self):
1292 """Returns a string of html with the given content type. E.g.,
1293 /contenttype?text/css returns an html file with the Content-Type
1294 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001295 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001296 return False
1297 query_char = self.path.find('?')
1298 content_type = self.path[query_char + 1:].strip()
1299 if not content_type:
1300 content_type = 'text/html'
1301 self.send_response(200)
1302 self.send_header('Content-Type', content_type)
1303 self.end_headers()
1304 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1305 return True
1306
creis@google.com2f4f6a42011-03-25 19:44:19 +00001307 def NoContentHandler(self):
1308 """Returns a 204 No Content response."""
1309 if not self._ShouldHandleRequest("/nocontent"):
1310 return False
1311 self.send_response(204)
1312 self.end_headers()
1313 return True
1314
initial.commit94958cf2008-07-26 22:42:52 +00001315 def ServerRedirectHandler(self):
1316 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001317 '/server-redirect?http://foo.bar/asdf' to redirect to
1318 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001319
1320 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001321 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001322 return False
1323
1324 query_char = self.path.find('?')
1325 if query_char < 0 or len(self.path) <= query_char + 1:
1326 self.sendRedirectHelp(test_name)
1327 return True
1328 dest = self.path[query_char + 1:]
1329
1330 self.send_response(301) # moved permanently
1331 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001332 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001333 self.end_headers()
1334 self.wfile.write('<html><head>')
1335 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1336
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001337 return True
initial.commit94958cf2008-07-26 22:42:52 +00001338
1339 def ClientRedirectHandler(self):
1340 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001341 '/client-redirect?http://foo.bar/asdf' to redirect to
1342 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001343
1344 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001345 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001346 return False
1347
1348 query_char = self.path.find('?');
1349 if query_char < 0 or len(self.path) <= query_char + 1:
1350 self.sendRedirectHelp(test_name)
1351 return True
1352 dest = self.path[query_char + 1:]
1353
1354 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001355 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001356 self.end_headers()
1357 self.wfile.write('<html><head>')
1358 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1359 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1360
1361 return True
1362
tony@chromium.org03266982010-03-05 03:18:42 +00001363 def MultipartHandler(self):
1364 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001365 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001366 if not self._ShouldHandleRequest(test_name):
1367 return False
1368
1369 num_frames = 10
1370 bound = '12345'
1371 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001372 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001373 'multipart/x-mixed-replace;boundary=' + bound)
1374 self.end_headers()
1375
1376 for i in xrange(num_frames):
1377 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001378 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001379 self.wfile.write('<title>page ' + str(i) + '</title>')
1380 self.wfile.write('page ' + str(i))
1381
1382 self.wfile.write('--' + bound + '--')
1383 return True
1384
tony@chromium.org4cb88302011-09-27 22:13:49 +00001385 def MultipartSlowHandler(self):
1386 """Send a multipart response (3 text/html pages) with a slight delay
1387 between each page. This is similar to how some pages show status using
1388 multipart."""
1389 test_name = '/multipart-slow'
1390 if not self._ShouldHandleRequest(test_name):
1391 return False
1392
1393 num_frames = 3
1394 bound = '12345'
1395 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001396 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001397 'multipart/x-mixed-replace;boundary=' + bound)
1398 self.end_headers()
1399
1400 for i in xrange(num_frames):
1401 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001402 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001403 time.sleep(0.25)
1404 if i == 2:
1405 self.wfile.write('<title>PASS</title>')
1406 else:
1407 self.wfile.write('<title>page ' + str(i) + '</title>')
1408 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1409
1410 self.wfile.write('--' + bound + '--')
1411 return True
1412
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001413 def GetSSLSessionCacheHandler(self):
1414 """Send a reply containing a log of the session cache operations."""
1415
1416 if not self._ShouldHandleRequest('/ssl-session-cache'):
1417 return False
1418
1419 self.send_response(200)
1420 self.send_header('Content-Type', 'text/plain')
1421 self.end_headers()
1422 try:
1423 for (action, sessionID) in self.server.session_cache.log:
1424 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1425 except AttributeError, e:
1426 self.wfile.write('Pass --https-record-resume in order to use' +
1427 ' this request')
1428 return True
1429
initial.commit94958cf2008-07-26 22:42:52 +00001430 def DefaultResponseHandler(self):
1431 """This is the catch-all response handler for requests that aren't handled
1432 by one of the special handlers above.
1433 Note that we specify the content-length as without it the https connection
1434 is not closed properly (and the browser keeps expecting data)."""
1435
1436 contents = "Default response given for path: " + self.path
1437 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001438 self.send_header('Content-Type', 'text/html')
1439 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001440 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001441 if (self.command != 'HEAD'):
1442 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001443 return True
1444
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001445 def RedirectConnectHandler(self):
1446 """Sends a redirect to the CONNECT request for www.redirect.com. This
1447 response is not specified by the RFC, so the browser should not follow
1448 the redirect."""
1449
1450 if (self.path.find("www.redirect.com") < 0):
1451 return False
1452
1453 dest = "http://www.destination.com/foo.js"
1454
1455 self.send_response(302) # moved temporarily
1456 self.send_header('Location', dest)
1457 self.send_header('Connection', 'close')
1458 self.end_headers()
1459 return True
1460
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001461 def ServerAuthConnectHandler(self):
1462 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1463 response doesn't make sense because the proxy server cannot request
1464 server authentication."""
1465
1466 if (self.path.find("www.server-auth.com") < 0):
1467 return False
1468
1469 challenge = 'Basic realm="WallyWorld"'
1470
1471 self.send_response(401) # unauthorized
1472 self.send_header('WWW-Authenticate', challenge)
1473 self.send_header('Connection', 'close')
1474 self.end_headers()
1475 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001476
1477 def DefaultConnectResponseHandler(self):
1478 """This is the catch-all response handler for CONNECT requests that aren't
1479 handled by one of the special handlers above. Real Web servers respond
1480 with 400 to CONNECT requests."""
1481
1482 contents = "Your client has issued a malformed or illegal request."
1483 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001484 self.send_header('Content-Type', 'text/html')
1485 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001486 self.end_headers()
1487 self.wfile.write(contents)
1488 return True
1489
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001490 def DeviceManagementHandler(self):
1491 """Delegates to the device management service used for cloud policy."""
1492 if not self._ShouldHandleRequest("/device_management"):
1493 return False
1494
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001495 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001496
1497 if not self.server._device_management_handler:
1498 import device_management
1499 policy_path = os.path.join(self.server.data_dir, 'device_management')
1500 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001501 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001502 self.server.policy_keys,
1503 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001504
1505 http_response, raw_reply = (
1506 self.server._device_management_handler.HandleRequest(self.path,
1507 self.headers,
1508 raw_request))
1509 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001510 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001511 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001512 self.end_headers()
1513 self.wfile.write(raw_reply)
1514 return True
1515
initial.commit94958cf2008-07-26 22:42:52 +00001516 # called by the redirect handling function when there is no parameter
1517 def sendRedirectHelp(self, redirect_name):
1518 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001519 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001520 self.end_headers()
1521 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1522 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1523 self.wfile.write('</body></html>')
1524
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001525 # called by chunked handling function
1526 def sendChunkHelp(self, chunk):
1527 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1528 self.wfile.write('%X\r\n' % len(chunk))
1529 self.wfile.write(chunk)
1530 self.wfile.write('\r\n')
1531
akalin@chromium.org154bb132010-11-12 02:20:27 +00001532
1533class SyncPageHandler(BasePageHandler):
1534 """Handler for the main HTTP sync server."""
1535
1536 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001537 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001538 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001539 self.ChromiumSyncDisableNotificationsOpHandler,
1540 self.ChromiumSyncEnableNotificationsOpHandler,
1541 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001542 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001543 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001544 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001545 self.ChromiumSyncErrorOpHandler,
1546 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001547
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001548 post_handlers = [self.ChromiumSyncCommandHandler,
1549 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001550 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001551 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001552 post_handlers, [])
1553
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001554
akalin@chromium.org154bb132010-11-12 02:20:27 +00001555 def ChromiumSyncTimeHandler(self):
1556 """Handle Chromium sync .../time requests.
1557
1558 The syncer sometimes checks server reachability by examining /time.
1559 """
1560 test_name = "/chromiumsync/time"
1561 if not self._ShouldHandleRequest(test_name):
1562 return False
1563
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001564 # Chrome hates it if we send a response before reading the request.
1565 if self.headers.getheader('content-length'):
1566 length = int(self.headers.getheader('content-length'))
1567 raw_request = self.rfile.read(length)
1568
akalin@chromium.org154bb132010-11-12 02:20:27 +00001569 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001570 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001571 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001572 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001573 return True
1574
1575 def ChromiumSyncCommandHandler(self):
1576 """Handle a chromiumsync command arriving via http.
1577
1578 This covers all sync protocol commands: authentication, getupdates, and
1579 commit.
1580 """
1581 test_name = "/chromiumsync/command"
1582 if not self._ShouldHandleRequest(test_name):
1583 return False
1584
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001585 length = int(self.headers.getheader('content-length'))
1586 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001587 http_response = 200
1588 raw_reply = None
1589 if not self.server.GetAuthenticated():
1590 http_response = 401
1591 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1592 else:
1593 http_response, raw_reply = self.server.HandleCommand(
1594 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001595
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001596 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001597 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001598 if http_response == 401:
1599 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001600 self.end_headers()
1601 self.wfile.write(raw_reply)
1602 return True
1603
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001604 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001605 test_name = "/chromiumsync/migrate"
1606 if not self._ShouldHandleRequest(test_name):
1607 return False
1608
1609 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1610 self.path)
1611 self.send_response(http_response)
1612 self.send_header('Content-Type', 'text/html')
1613 self.send_header('Content-Length', len(raw_reply))
1614 self.end_headers()
1615 self.wfile.write(raw_reply)
1616 return True
1617
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001618 def ChromiumSyncCredHandler(self):
1619 test_name = "/chromiumsync/cred"
1620 if not self._ShouldHandleRequest(test_name):
1621 return False
1622 try:
1623 query = urlparse.urlparse(self.path)[4]
1624 cred_valid = urlparse.parse_qs(query)['valid']
1625 if cred_valid[0] == 'True':
1626 self.server.SetAuthenticated(True)
1627 else:
1628 self.server.SetAuthenticated(False)
1629 except:
1630 self.server.SetAuthenticated(False)
1631
1632 http_response = 200
1633 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1634 self.send_response(http_response)
1635 self.send_header('Content-Type', 'text/html')
1636 self.send_header('Content-Length', len(raw_reply))
1637 self.end_headers()
1638 self.wfile.write(raw_reply)
1639 return True
1640
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001641 def ChromiumSyncDisableNotificationsOpHandler(self):
1642 test_name = "/chromiumsync/disablenotifications"
1643 if not self._ShouldHandleRequest(test_name):
1644 return False
1645 self.server.GetXmppServer().DisableNotifications()
1646 result = 200
1647 raw_reply = ('<html><title>Notifications disabled</title>'
1648 '<H1>Notifications disabled</H1></html>')
1649 self.send_response(result)
1650 self.send_header('Content-Type', 'text/html')
1651 self.send_header('Content-Length', len(raw_reply))
1652 self.end_headers()
1653 self.wfile.write(raw_reply)
1654 return True;
1655
1656 def ChromiumSyncEnableNotificationsOpHandler(self):
1657 test_name = "/chromiumsync/enablenotifications"
1658 if not self._ShouldHandleRequest(test_name):
1659 return False
1660 self.server.GetXmppServer().EnableNotifications()
1661 result = 200
1662 raw_reply = ('<html><title>Notifications enabled</title>'
1663 '<H1>Notifications enabled</H1></html>')
1664 self.send_response(result)
1665 self.send_header('Content-Type', 'text/html')
1666 self.send_header('Content-Length', len(raw_reply))
1667 self.end_headers()
1668 self.wfile.write(raw_reply)
1669 return True;
1670
1671 def ChromiumSyncSendNotificationOpHandler(self):
1672 test_name = "/chromiumsync/sendnotification"
1673 if not self._ShouldHandleRequest(test_name):
1674 return False
1675 query = urlparse.urlparse(self.path)[4]
1676 query_params = urlparse.parse_qs(query)
1677 channel = ''
1678 data = ''
1679 if 'channel' in query_params:
1680 channel = query_params['channel'][0]
1681 if 'data' in query_params:
1682 data = query_params['data'][0]
1683 self.server.GetXmppServer().SendNotification(channel, data)
1684 result = 200
1685 raw_reply = ('<html><title>Notification sent</title>'
1686 '<H1>Notification sent with channel "%s" '
1687 'and data "%s"</H1></html>'
1688 % (channel, data))
1689 self.send_response(result)
1690 self.send_header('Content-Type', 'text/html')
1691 self.send_header('Content-Length', len(raw_reply))
1692 self.end_headers()
1693 self.wfile.write(raw_reply)
1694 return True;
1695
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001696 def ChromiumSyncBirthdayErrorOpHandler(self):
1697 test_name = "/chromiumsync/birthdayerror"
1698 if not self._ShouldHandleRequest(test_name):
1699 return False
1700 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1701 self.send_response(result)
1702 self.send_header('Content-Type', 'text/html')
1703 self.send_header('Content-Length', len(raw_reply))
1704 self.end_headers()
1705 self.wfile.write(raw_reply)
1706 return True;
1707
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001708 def ChromiumSyncTransientErrorOpHandler(self):
1709 test_name = "/chromiumsync/transienterror"
1710 if not self._ShouldHandleRequest(test_name):
1711 return False
1712 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1713 self.send_response(result)
1714 self.send_header('Content-Type', 'text/html')
1715 self.send_header('Content-Length', len(raw_reply))
1716 self.end_headers()
1717 self.wfile.write(raw_reply)
1718 return True;
1719
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001720 def ChromiumSyncErrorOpHandler(self):
1721 test_name = "/chromiumsync/error"
1722 if not self._ShouldHandleRequest(test_name):
1723 return False
1724 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1725 self.path)
1726 self.send_response(result)
1727 self.send_header('Content-Type', 'text/html')
1728 self.send_header('Content-Length', len(raw_reply))
1729 self.end_headers()
1730 self.wfile.write(raw_reply)
1731 return True;
1732
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001733 def ChromiumSyncSyncTabsOpHandler(self):
1734 test_name = "/chromiumsync/synctabs"
1735 if not self._ShouldHandleRequest(test_name):
1736 return False
1737 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1738 self.send_response(result)
1739 self.send_header('Content-Type', 'text/html')
1740 self.send_header('Content-Length', len(raw_reply))
1741 self.end_headers()
1742 self.wfile.write(raw_reply)
1743 return True;
1744
akalin@chromium.org154bb132010-11-12 02:20:27 +00001745
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001746def MakeDataDir():
1747 if options.data_dir:
1748 if not os.path.isdir(options.data_dir):
1749 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1750 return None
1751 my_data_dir = options.data_dir
1752 else:
1753 # Create the default path to our data dir, relative to the exe dir.
1754 my_data_dir = os.path.dirname(sys.argv[0])
1755 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001756 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001757
1758 #TODO(ibrar): Must use Find* funtion defined in google\tools
1759 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1760
1761 return my_data_dir
1762
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001763
1764class TCPEchoHandler(SocketServer.BaseRequestHandler):
1765 """The RequestHandler class for TCP echo server.
1766
1767 It is instantiated once per connection to the server, and overrides the
1768 handle() method to implement communication to the client.
1769 """
1770
1771 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001772 """Handles the request from the client and constructs a response."""
1773
1774 data = self.request.recv(65536).strip()
1775 # Verify the "echo request" message received from the client. Send back
1776 # "echo response" message if "echo request" message is valid.
1777 try:
1778 return_data = echo_message.GetEchoResponseData(data)
1779 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001780 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001781 except ValueError:
1782 return
1783
1784 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001785
1786
1787class UDPEchoHandler(SocketServer.BaseRequestHandler):
1788 """The RequestHandler class for UDP echo server.
1789
1790 It is instantiated once per connection to the server, and overrides the
1791 handle() method to implement communication to the client.
1792 """
1793
1794 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001795 """Handles the request from the client and constructs a response."""
1796
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001797 data = self.request[0].strip()
1798 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001799 # Verify the "echo request" message received from the client. Send back
1800 # "echo response" message if "echo request" message is valid.
1801 try:
1802 return_data = echo_message.GetEchoResponseData(data)
1803 if not return_data:
1804 return
1805 except ValueError:
1806 return
1807 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001808
1809
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001810class FileMultiplexer:
1811 def __init__(self, fd1, fd2) :
1812 self.__fd1 = fd1
1813 self.__fd2 = fd2
1814
1815 def __del__(self) :
1816 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1817 self.__fd1.close()
1818 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1819 self.__fd2.close()
1820
1821 def write(self, text) :
1822 self.__fd1.write(text)
1823 self.__fd2.write(text)
1824
1825 def flush(self) :
1826 self.__fd1.flush()
1827 self.__fd2.flush()
1828
initial.commit94958cf2008-07-26 22:42:52 +00001829def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001830 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001831 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001832 if options.log_to_console:
1833 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1834 else:
1835 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001836
1837 port = options.port
1838
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001839 server_data = {}
1840
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001841 if options.server_type == SERVER_HTTP:
1842 if options.cert:
1843 # let's make sure the cert file exists.
1844 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001845 print 'specified server cert file not found: ' + options.cert + \
1846 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001847 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001848 for ca_cert in options.ssl_client_ca:
1849 if not os.path.isfile(ca_cert):
1850 print 'specified trusted client CA file not found: ' + ca_cert + \
1851 ' exiting...'
1852 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001853 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001854 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001855 options.ssl_bulk_cipher, options.record_resume)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001856 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001857 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001858 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001859 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001860
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001861 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001862 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001863 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001864 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001865 server.policy_keys = options.policy_keys
1866 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001867 elif options.server_type == SERVER_SYNC:
1868 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1869 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001870 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1871 server_data['port'] = server.server_port
1872 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001873 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001874 # Used for generating the key (randomly) that encodes the "echo request"
1875 # message.
1876 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001877 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1878 print 'Echo TCP server started on port %d...' % server.server_port
1879 server_data['port'] = server.server_port
1880 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001881 # Used for generating the key (randomly) that encodes the "echo request"
1882 # message.
1883 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001884 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1885 print 'Echo UDP server started on port %d...' % server.server_port
1886 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001887 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001888 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001889 my_data_dir = MakeDataDir()
1890
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001891 # Instantiate a dummy authorizer for managing 'virtual' users
1892 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1893
1894 # Define a new user having full r/w permissions and a read-only
1895 # anonymous user
1896 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1897
1898 authorizer.add_anonymous(my_data_dir)
1899
1900 # Instantiate FTP handler class
1901 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1902 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001903
1904 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001905 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1906 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001907
1908 # Instantiate FTP server class and listen to 127.0.0.1:port
1909 address = ('127.0.0.1', port)
1910 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001911 server_data['port'] = server.socket.getsockname()[1]
1912 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001913
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001914 # Notify the parent that we've started. (BaseServer subclasses
1915 # bind their sockets on construction.)
1916 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001917 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001918 server_data_len = len(server_data_json)
1919 print 'sending server_data: %s (%d bytes)' % (
1920 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001921 if sys.platform == 'win32':
1922 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1923 else:
1924 fd = options.startup_pipe
1925 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001926 # First write the data length as an unsigned 4-byte value. This
1927 # is _not_ using network byte ordering since the other end of the
1928 # pipe is on the same machine.
1929 startup_pipe.write(struct.pack('=L', server_data_len))
1930 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001931 startup_pipe.close()
1932
initial.commit94958cf2008-07-26 22:42:52 +00001933 try:
1934 server.serve_forever()
1935 except KeyboardInterrupt:
1936 print 'shutting down server'
1937 server.stop = True
1938
1939if __name__ == '__main__':
1940 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001941 option_parser.add_option("-f", '--ftp', action='store_const',
1942 const=SERVER_FTP, default=SERVER_HTTP,
1943 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001944 help='start up an FTP server.')
1945 option_parser.add_option('', '--sync', action='store_const',
1946 const=SERVER_SYNC, default=SERVER_HTTP,
1947 dest='server_type',
1948 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001949 option_parser.add_option('', '--tcp-echo', action='store_const',
1950 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1951 dest='server_type',
1952 help='start up a tcp echo server.')
1953 option_parser.add_option('', '--udp-echo', action='store_const',
1954 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1955 dest='server_type',
1956 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001957 option_parser.add_option('', '--log-to-console', action='store_const',
1958 const=True, default=False,
1959 dest='log_to_console',
1960 help='Enables or disables sys.stdout logging to '
1961 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001962 option_parser.add_option('', '--port', default='0', type='int',
1963 help='Port used by the server. If unspecified, the '
1964 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001965 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001966 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001967 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001968 help='Specify that https should be used, specify '
1969 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001970 'the server should use.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001971 option_parser.add_option('', '--https-record-resume', dest='record_resume',
1972 const=True, default=False, action='store_const',
1973 help='Record resumption cache events rather than'
1974 ' resuming as normal. Allows the use of the'
1975 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001976 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1977 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001978 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1979 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001980 'should include the CA named in the subject of '
1981 'the DER-encoded certificate contained in the '
1982 'specified file. This option may appear multiple '
1983 'times, indicating multiple CA names should be '
1984 'sent in the request.')
1985 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1986 help='Specify the bulk encryption algorithm(s)'
1987 'that will be accepted by the SSL server. Valid '
1988 'values are "aes256", "aes128", "3des", "rc4". If '
1989 'omitted, all algorithms will be used. This '
1990 'option may appear multiple times, indicating '
1991 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001992 option_parser.add_option('', '--file-root-url', default='/files/',
1993 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001994 option_parser.add_option('', '--startup-pipe', type='int',
1995 dest='startup_pipe',
1996 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001997 option_parser.add_option('', '--policy-key', action='append',
1998 dest='policy_keys',
1999 help='Specify a path to a PEM-encoded private key '
2000 'to use for policy signing. May be specified '
2001 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002002 'the server. If ther server has multiple keys, it '
2003 'will rotate through them in at each request a '
2004 'round-robin fashion. The server will generate a '
2005 'random key if none is specified on the command '
2006 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002007 option_parser.add_option('', '--policy-user', default='user@example.com',
2008 dest='policy_user',
2009 help='Specify the user name the server should '
2010 'report back to the client as the user owning the '
2011 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00002012 options, args = option_parser.parse_args()
2013
2014 sys.exit(main(options, args))