blob: 35a08c465c07b2d8702327815824457c3c383b6b [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00002# Copyright (c) 2011 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
initial.commit94958cf2008-07-26 22:42:52 +000021import optparse
22import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000023import random
initial.commit94958cf2008-07-26 22:42:52 +000024import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000025import select
initial.commit94958cf2008-07-26 22:42:52 +000026import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import socket
initial.commit94958cf2008-07-26 22:42:52 +000028import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000029import struct
initial.commit94958cf2008-07-26 22:42:52 +000030import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000031import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000032import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000033import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000034import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000035
36# Ignore deprecation warnings, they make our output more cluttered.
37warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000039import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000040import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000041import tlslite
42import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000044try:
45 import hashlib
46 _new_md5 = hashlib.md5
47except ImportError:
48 import md5
49 _new_md5 = md5.new
50
dpranke@chromium.org70049b72011-10-14 00:38:18 +000051try:
52 import json
53except ImportError:
54 import simplejson as json
55
davidben@chromium.org06fcf202010-09-22 18:15:23 +000056if sys.platform == 'win32':
57 import msvcrt
58
maruel@chromium.org756cf982009-03-05 12:46:38 +000059SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000060SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000061SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000062SERVER_TCP_ECHO = 3
63SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000064
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000065# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000066debug_output = sys.stderr
67def debug(str):
68 debug_output.write(str + "\n")
69 debug_output.flush()
70
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,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000385 self.DeviceManagementHandler] + get_handlers
386 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000387 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000388 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000389 head_handlers = [
390 self.FileHandler,
391 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000392
maruel@google.come250a9b2009-03-10 17:39:46 +0000393 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000394 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000395 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000396 'gif': 'image/gif',
397 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000398 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000399 'pdf' : 'application/pdf',
400 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000401 }
initial.commit94958cf2008-07-26 22:42:52 +0000402 self._default_mime_type = 'text/html'
403
akalin@chromium.org154bb132010-11-12 02:20:27 +0000404 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000405 connect_handlers, get_handlers, head_handlers,
406 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000407
initial.commit94958cf2008-07-26 22:42:52 +0000408 def GetMIMETypeFromName(self, file_name):
409 """Returns the mime type for the specified file_name. So far it only looks
410 at the file extension."""
411
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000412 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000413 if len(extension) == 0:
414 # no extension.
415 return self._default_mime_type
416
ericroman@google.comc17ca532009-05-07 03:51:05 +0000417 # extension starts with a dot, so we need to remove it
418 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000419
initial.commit94958cf2008-07-26 22:42:52 +0000420 def NoCacheMaxAgeTimeHandler(self):
421 """This request handler yields a page with the title set to the current
422 system time, and no caching requested."""
423
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000424 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000425 return False
426
427 self.send_response(200)
428 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000429 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000430 self.end_headers()
431
maruel@google.come250a9b2009-03-10 17:39:46 +0000432 self.wfile.write('<html><head><title>%s</title></head></html>' %
433 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000434
435 return True
436
437 def NoCacheTimeHandler(self):
438 """This request handler yields a page with the title set to the current
439 system time, and no caching requested."""
440
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000441 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000442 return False
443
444 self.send_response(200)
445 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000446 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000447 self.end_headers()
448
maruel@google.come250a9b2009-03-10 17:39:46 +0000449 self.wfile.write('<html><head><title>%s</title></head></html>' %
450 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000451
452 return True
453
454 def CacheTimeHandler(self):
455 """This request handler yields a page with the title set to the current
456 system time, and allows caching for one minute."""
457
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000458 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000459 return False
460
461 self.send_response(200)
462 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000463 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000464 self.end_headers()
465
maruel@google.come250a9b2009-03-10 17:39:46 +0000466 self.wfile.write('<html><head><title>%s</title></head></html>' %
467 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000468
469 return True
470
471 def CacheExpiresHandler(self):
472 """This request handler yields a page with the title set to the current
473 system time, and set the page to expire on 1 Jan 2099."""
474
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000475 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000476 return False
477
478 self.send_response(200)
479 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000480 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.end_headers()
482
maruel@google.come250a9b2009-03-10 17:39:46 +0000483 self.wfile.write('<html><head><title>%s</title></head></html>' %
484 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000485
486 return True
487
488 def CacheProxyRevalidateHandler(self):
489 """This request handler yields a page with the title set to the current
490 system time, and allows caching for 60 seconds"""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000496 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000497 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
498 self.end_headers()
499
maruel@google.come250a9b2009-03-10 17:39:46 +0000500 self.wfile.write('<html><head><title>%s</title></head></html>' %
501 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000502
503 return True
504
505 def CachePrivateHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and allows caching for 5 seconds."""
508
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000509 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000510 return False
511
512 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000513 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000514 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000515 self.end_headers()
516
maruel@google.come250a9b2009-03-10 17:39:46 +0000517 self.wfile.write('<html><head><title>%s</title></head></html>' %
518 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000519
520 return True
521
522 def CachePublicHandler(self):
523 """This request handler yields a page with the title set to the current
524 system time, and allows caching for 5 seconds."""
525
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000526 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000527 return False
528
529 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000530 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000531 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000532 self.end_headers()
533
maruel@google.come250a9b2009-03-10 17:39:46 +0000534 self.wfile.write('<html><head><title>%s</title></head></html>' %
535 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000536
537 return True
538
539 def CacheSMaxAgeHandler(self):
540 """This request handler yields a page with the title set to the current
541 system time, and does not allow for caching."""
542
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000543 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000544 return False
545
546 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000547 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000548 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
549 self.end_headers()
550
maruel@google.come250a9b2009-03-10 17:39:46 +0000551 self.wfile.write('<html><head><title>%s</title></head></html>' %
552 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000553
554 return True
555
556 def CacheMustRevalidateHandler(self):
557 """This request handler yields a page with the title set to the current
558 system time, and does not allow caching."""
559
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000560 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000561 return False
562
563 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000564 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000565 self.send_header('Cache-Control', 'must-revalidate')
566 self.end_headers()
567
maruel@google.come250a9b2009-03-10 17:39:46 +0000568 self.wfile.write('<html><head><title>%s</title></head></html>' %
569 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000570
571 return True
572
573 def CacheMustRevalidateMaxAgeHandler(self):
574 """This request handler yields a page with the title set to the current
575 system time, and does not allow caching event though max-age of 60
576 seconds is specified."""
577
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000578 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000579 return False
580
581 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000582 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000583 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
584 self.end_headers()
585
maruel@google.come250a9b2009-03-10 17:39:46 +0000586 self.wfile.write('<html><head><title>%s</title></head></html>' %
587 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000588
589 return True
590
initial.commit94958cf2008-07-26 22:42:52 +0000591 def CacheNoStoreHandler(self):
592 """This request handler yields a page with the title set to the current
593 system time, and does not allow the page to be stored."""
594
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000595 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000596 return False
597
598 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000599 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000600 self.send_header('Cache-Control', 'no-store')
601 self.end_headers()
602
maruel@google.come250a9b2009-03-10 17:39:46 +0000603 self.wfile.write('<html><head><title>%s</title></head></html>' %
604 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000605
606 return True
607
608 def CacheNoStoreMaxAgeHandler(self):
609 """This request handler yields a page with the title set to the current
610 system time, and does not allow the page to be stored even though max-age
611 of 60 seconds is specified."""
612
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000613 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000614 return False
615
616 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000617 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000618 self.send_header('Cache-Control', 'max-age=60, no-store')
619 self.end_headers()
620
maruel@google.come250a9b2009-03-10 17:39:46 +0000621 self.wfile.write('<html><head><title>%s</title></head></html>' %
622 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000623
624 return True
625
626
627 def CacheNoTransformHandler(self):
628 """This request handler yields a page with the title set to the current
629 system time, and does not allow the content to transformed during
630 user-agent caching"""
631
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000632 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000633 return False
634
635 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000636 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000637 self.send_header('Cache-Control', 'no-transform')
638 self.end_headers()
639
maruel@google.come250a9b2009-03-10 17:39:46 +0000640 self.wfile.write('<html><head><title>%s</title></head></html>' %
641 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000642
643 return True
644
645 def EchoHeader(self):
646 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000647 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000648
ananta@chromium.org56812d02011-04-07 17:52:05 +0000649 """This function echoes back the value of a specific request header"""
650 """while allowing caching for 16 hours."""
651 def EchoHeaderCache(self):
652 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000653
654 def EchoHeaderHelper(self, echo_header):
655 """This function echoes back the value of the request header passed in."""
656 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000657 return False
658
659 query_char = self.path.find('?')
660 if query_char != -1:
661 header_name = self.path[query_char+1:]
662
663 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000664 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000665 if echo_header == '/echoheadercache':
666 self.send_header('Cache-control', 'max-age=60000')
667 else:
668 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000669 # insert a vary header to properly indicate that the cachability of this
670 # request is subject to value of the request header being echoed.
671 if len(header_name) > 0:
672 self.send_header('Vary', header_name)
673 self.end_headers()
674
675 if len(header_name) > 0:
676 self.wfile.write(self.headers.getheader(header_name))
677
678 return True
679
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000680 def ReadRequestBody(self):
681 """This function reads the body of the current HTTP request, handling
682 both plain and chunked transfer encoded requests."""
683
684 if self.headers.getheader('transfer-encoding') != 'chunked':
685 length = int(self.headers.getheader('content-length'))
686 return self.rfile.read(length)
687
688 # Read the request body as chunks.
689 body = ""
690 while True:
691 line = self.rfile.readline()
692 length = int(line, 16)
693 if length == 0:
694 self.rfile.readline()
695 break
696 body += self.rfile.read(length)
697 self.rfile.read(2)
698 return body
699
initial.commit94958cf2008-07-26 22:42:52 +0000700 def EchoHandler(self):
701 """This handler just echoes back the payload of the request, for testing
702 form submission."""
703
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000704 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000705 return False
706
707 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000708 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000709 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000710 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000711 return True
712
713 def EchoTitleHandler(self):
714 """This handler is like Echo, but sets the page title to the request."""
715
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000716 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000717 return False
718
719 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000720 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000721 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000722 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000723 self.wfile.write('<html><head><title>')
724 self.wfile.write(request)
725 self.wfile.write('</title></head></html>')
726 return True
727
728 def EchoAllHandler(self):
729 """This handler yields a (more) human-readable page listing information
730 about the request header & contents."""
731
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000732 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000733 return False
734
735 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000736 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000737 self.end_headers()
738 self.wfile.write('<html><head><style>'
739 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
740 '</style></head><body>'
741 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000742 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000743 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000744
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000745 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000746 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000747 params = cgi.parse_qs(qs, keep_blank_values=1)
748
749 for param in params:
750 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000751
752 self.wfile.write('</pre>')
753
754 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
755
756 self.wfile.write('</body></html>')
757 return True
758
759 def DownloadHandler(self):
760 """This handler sends a downloadable file with or without reporting
761 the size (6K)."""
762
763 if self.path.startswith("/download-unknown-size"):
764 send_length = False
765 elif self.path.startswith("/download-known-size"):
766 send_length = True
767 else:
768 return False
769
770 #
771 # The test which uses this functionality is attempting to send
772 # small chunks of data to the client. Use a fairly large buffer
773 # so that we'll fill chrome's IO buffer enough to force it to
774 # actually write the data.
775 # See also the comments in the client-side of this test in
776 # download_uitest.cc
777 #
778 size_chunk1 = 35*1024
779 size_chunk2 = 10*1024
780
781 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000782 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000783 self.send_header('Cache-Control', 'max-age=0')
784 if send_length:
785 self.send_header('Content-Length', size_chunk1 + size_chunk2)
786 self.end_headers()
787
788 # First chunk of data:
789 self.wfile.write("*" * size_chunk1)
790 self.wfile.flush()
791
792 # handle requests until one of them clears this flag.
793 self.server.waitForDownload = True
794 while self.server.waitForDownload:
795 self.server.handle_request()
796
797 # Second chunk of data:
798 self.wfile.write("*" * size_chunk2)
799 return True
800
801 def DownloadFinishHandler(self):
802 """This handler just tells the server to finish the current download."""
803
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000804 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000805 return False
806
807 self.server.waitForDownload = False
808 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000809 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.send_header('Cache-Control', 'max-age=0')
811 self.end_headers()
812 return True
813
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000814 def _ReplaceFileData(self, data, query_parameters):
815 """Replaces matching substrings in a file.
816
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000817 If the 'replace_text' URL query parameter is present, it is expected to be
818 of the form old_text:new_text, which indicates that any old_text strings in
819 the file are replaced with new_text. Multiple 'replace_text' parameters may
820 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000821
822 If the parameters are not present, |data| is returned.
823 """
824 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000825 replace_text_values = query_dict.get('replace_text', [])
826 for replace_text_value in replace_text_values:
827 replace_text_args = replace_text_value.split(':')
828 if len(replace_text_args) != 2:
829 raise ValueError(
830 'replace_text must be of form old_text:new_text. Actual value: %s' %
831 replace_text_value)
832 old_text_b64, new_text_b64 = replace_text_args
833 old_text = base64.urlsafe_b64decode(old_text_b64)
834 new_text = base64.urlsafe_b64decode(new_text_b64)
835 data = data.replace(old_text, new_text)
836 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000837
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000838 def ZipFileHandler(self):
839 """This handler sends the contents of the requested file in compressed form.
840 Can pass in a parameter that specifies that the content length be
841 C - the compressed size (OK),
842 U - the uncompressed size (Non-standard, but handled),
843 S - less than compressed (OK because we keep going),
844 M - larger than compressed but less than uncompressed (an error),
845 L - larger than uncompressed (an error)
846 Example: compressedfiles/Picture_1.doc?C
847 """
848
849 prefix = "/compressedfiles/"
850 if not self.path.startswith(prefix):
851 return False
852
853 # Consume a request body if present.
854 if self.command == 'POST' or self.command == 'PUT' :
855 self.ReadRequestBody()
856
857 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
858
859 if not query in ('C', 'U', 'S', 'M', 'L'):
860 return False
861
862 sub_path = url_path[len(prefix):]
863 entries = sub_path.split('/')
864 file_path = os.path.join(self.server.data_dir, *entries)
865 if os.path.isdir(file_path):
866 file_path = os.path.join(file_path, 'index.html')
867
868 if not os.path.isfile(file_path):
869 print "File not found " + sub_path + " full path:" + file_path
870 self.send_error(404)
871 return True
872
873 f = open(file_path, "rb")
874 data = f.read()
875 uncompressed_len = len(data)
876 f.close()
877
878 # Compress the data.
879 data = zlib.compress(data)
880 compressed_len = len(data)
881
882 content_length = compressed_len
883 if query == 'U':
884 content_length = uncompressed_len
885 elif query == 'S':
886 content_length = compressed_len / 2
887 elif query == 'M':
888 content_length = (compressed_len + uncompressed_len) / 2
889 elif query == 'L':
890 content_length = compressed_len + uncompressed_len
891
892 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000893 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000894 self.send_header('Content-encoding', 'deflate')
895 self.send_header('Connection', 'close')
896 self.send_header('Content-Length', content_length)
897 self.send_header('ETag', '\'' + file_path + '\'')
898 self.end_headers()
899
900 self.wfile.write(data)
901
902 return True
903
initial.commit94958cf2008-07-26 22:42:52 +0000904 def FileHandler(self):
905 """This handler sends the contents of the requested file. Wow, it's like
906 a real webserver!"""
907
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
911
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000912 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000913 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000914 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000915
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000916 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
917 sub_path = url_path[len(prefix):]
918 entries = sub_path.split('/')
919 file_path = os.path.join(self.server.data_dir, *entries)
920 if os.path.isdir(file_path):
921 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000922
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000923 if not os.path.isfile(file_path):
924 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000925 self.send_error(404)
926 return True
927
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000928 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000929 data = f.read()
930 f.close()
931
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000932 data = self._ReplaceFileData(data, query)
933
initial.commit94958cf2008-07-26 22:42:52 +0000934 # If file.mock-http-headers exists, it contains the headers we
935 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000936 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000937 if os.path.isfile(headers_path):
938 f = open(headers_path, "r")
939
940 # "HTTP/1.1 200 OK"
941 response = f.readline()
942 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
943 self.send_response(int(status_code))
944
945 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000946 header_values = re.findall('(\S+):\s*(.*)', line)
947 if len(header_values) > 0:
948 # "name: value"
949 name, value = header_values[0]
950 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000951 f.close()
952 else:
953 # Could be more generic once we support mime-type sniffing, but for
954 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000955
956 range = self.headers.get('Range')
957 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +0000958 # Note this doesn't handle all valid byte range values (i.e. left
959 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +0000960 range = range[6:].split('-')
961 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000962 if range[1]:
963 end = int(range[1])
964 else:
965 end = len(data)
jam@chromium.org41550782010-11-17 23:47:50 +0000966
967 self.send_response(206)
968 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
969 str(len(data))
970 self.send_header('Content-Range', content_range)
971 data = data[start: end + 1]
972 else:
973 self.send_response(200)
974
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000975 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000976 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000977 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000978 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000979 self.end_headers()
980
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000981 if (self.command != 'HEAD'):
982 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000983
984 return True
985
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000986 def SetCookieHandler(self):
987 """This handler just sets a cookie, for testing cookie handling."""
988
989 if not self._ShouldHandleRequest("/set-cookie"):
990 return False
991
992 query_char = self.path.find('?')
993 if query_char != -1:
994 cookie_values = self.path[query_char + 1:].split('&')
995 else:
996 cookie_values = ("",)
997 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000998 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000999 for cookie_value in cookie_values:
1000 self.send_header('Set-Cookie', '%s' % cookie_value)
1001 self.end_headers()
1002 for cookie_value in cookie_values:
1003 self.wfile.write('%s' % cookie_value)
1004 return True
1005
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001006 def SetHeaderHandler(self):
1007 """This handler sets a response header. Parameters are in the
1008 key%3A%20value&key2%3A%20value2 format."""
1009
1010 if not self._ShouldHandleRequest("/set-header"):
1011 return False
1012
1013 query_char = self.path.find('?')
1014 if query_char != -1:
1015 headers_values = self.path[query_char + 1:].split('&')
1016 else:
1017 headers_values = ("",)
1018 self.send_response(200)
1019 self.send_header('Content-Type', 'text/html')
1020 for header_value in headers_values:
1021 header_value = urllib.unquote(header_value)
1022 (key, value) = header_value.split(': ', 1)
1023 self.send_header(key, value)
1024 self.end_headers()
1025 for header_value in headers_values:
1026 self.wfile.write('%s' % header_value)
1027 return True
1028
initial.commit94958cf2008-07-26 22:42:52 +00001029 def AuthBasicHandler(self):
1030 """This handler tests 'Basic' authentication. It just sends a page with
1031 title 'user/pass' if you succeed."""
1032
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001033 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001034 return False
1035
1036 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001037 expected_password = 'secret'
1038 realm = 'testrealm'
1039 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001040
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001041 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1042 query_params = cgi.parse_qs(query, True)
1043 if 'set-cookie-if-challenged' in query_params:
1044 set_cookie_if_challenged = True
1045 if 'password' in query_params:
1046 expected_password = query_params['password'][0]
1047 if 'realm' in query_params:
1048 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001049
initial.commit94958cf2008-07-26 22:42:52 +00001050 auth = self.headers.getheader('authorization')
1051 try:
1052 if not auth:
1053 raise Exception('no auth')
1054 b64str = re.findall(r'Basic (\S+)', auth)[0]
1055 userpass = base64.b64decode(b64str)
1056 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001057 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001058 raise Exception('wrong password')
1059 except Exception, e:
1060 # Authentication failed.
1061 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001062 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001063 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001064 if set_cookie_if_challenged:
1065 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001066 self.end_headers()
1067 self.wfile.write('<html><head>')
1068 self.wfile.write('<title>Denied: %s</title>' % e)
1069 self.wfile.write('</head><body>')
1070 self.wfile.write('auth=%s<p>' % auth)
1071 self.wfile.write('b64str=%s<p>' % b64str)
1072 self.wfile.write('username: %s<p>' % username)
1073 self.wfile.write('userpass: %s<p>' % userpass)
1074 self.wfile.write('password: %s<p>' % password)
1075 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1076 self.wfile.write('</body></html>')
1077 return True
1078
1079 # Authentication successful. (Return a cachable response to allow for
1080 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001081 old_protocol_version = self.protocol_version
1082 self.protocol_version = "HTTP/1.1"
1083
initial.commit94958cf2008-07-26 22:42:52 +00001084 if_none_match = self.headers.getheader('if-none-match')
1085 if if_none_match == "abc":
1086 self.send_response(304)
1087 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001088 elif url_path.endswith(".gif"):
1089 # Using chrome/test/data/google/logo.gif as the test image
1090 test_image_path = ['google', 'logo.gif']
1091 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1092 if not os.path.isfile(gif_path):
1093 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001094 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001095 return True
1096
1097 f = open(gif_path, "rb")
1098 data = f.read()
1099 f.close()
1100
1101 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001102 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001103 self.send_header('Cache-control', 'max-age=60000')
1104 self.send_header('Etag', 'abc')
1105 self.end_headers()
1106 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001107 else:
1108 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001109 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001110 self.send_header('Cache-control', 'max-age=60000')
1111 self.send_header('Etag', 'abc')
1112 self.end_headers()
1113 self.wfile.write('<html><head>')
1114 self.wfile.write('<title>%s/%s</title>' % (username, password))
1115 self.wfile.write('</head><body>')
1116 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001117 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001118 self.wfile.write('</body></html>')
1119
rvargas@google.com54453b72011-05-19 01:11:11 +00001120 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001121 return True
1122
tonyg@chromium.org75054202010-03-31 22:06:10 +00001123 def GetNonce(self, force_reset=False):
1124 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001125
tonyg@chromium.org75054202010-03-31 22:06:10 +00001126 This is a fake implementation. A real implementation would only use a given
1127 nonce a single time (hence the name n-once). However, for the purposes of
1128 unittesting, we don't care about the security of the nonce.
1129
1130 Args:
1131 force_reset: Iff set, the nonce will be changed. Useful for testing the
1132 "stale" response.
1133 """
1134 if force_reset or not self.server.nonce_time:
1135 self.server.nonce_time = time.time()
1136 return _new_md5('privatekey%s%d' %
1137 (self.path, self.server.nonce_time)).hexdigest()
1138
1139 def AuthDigestHandler(self):
1140 """This handler tests 'Digest' authentication.
1141
1142 It just sends a page with title 'user/pass' if you succeed.
1143
1144 A stale response is sent iff "stale" is present in the request path.
1145 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001146 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001147 return False
1148
tonyg@chromium.org75054202010-03-31 22:06:10 +00001149 stale = 'stale' in self.path
1150 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001151 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001152 password = 'secret'
1153 realm = 'testrealm'
1154
1155 auth = self.headers.getheader('authorization')
1156 pairs = {}
1157 try:
1158 if not auth:
1159 raise Exception('no auth')
1160 if not auth.startswith('Digest'):
1161 raise Exception('not digest')
1162 # Pull out all the name="value" pairs as a dictionary.
1163 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1164
1165 # Make sure it's all valid.
1166 if pairs['nonce'] != nonce:
1167 raise Exception('wrong nonce')
1168 if pairs['opaque'] != opaque:
1169 raise Exception('wrong opaque')
1170
1171 # Check the 'response' value and make sure it matches our magic hash.
1172 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001173 hash_a1 = _new_md5(
1174 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001175 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001176 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001177 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001178 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1179 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001180 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001181
1182 if pairs['response'] != response:
1183 raise Exception('wrong password')
1184 except Exception, e:
1185 # Authentication failed.
1186 self.send_response(401)
1187 hdr = ('Digest '
1188 'realm="%s", '
1189 'domain="/", '
1190 'qop="auth", '
1191 'algorithm=MD5, '
1192 'nonce="%s", '
1193 'opaque="%s"') % (realm, nonce, opaque)
1194 if stale:
1195 hdr += ', stale="TRUE"'
1196 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001197 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001198 self.end_headers()
1199 self.wfile.write('<html><head>')
1200 self.wfile.write('<title>Denied: %s</title>' % e)
1201 self.wfile.write('</head><body>')
1202 self.wfile.write('auth=%s<p>' % auth)
1203 self.wfile.write('pairs=%s<p>' % pairs)
1204 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1205 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1206 self.wfile.write('</body></html>')
1207 return True
1208
1209 # Authentication successful.
1210 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001211 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001212 self.end_headers()
1213 self.wfile.write('<html><head>')
1214 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1215 self.wfile.write('</head><body>')
1216 self.wfile.write('auth=%s<p>' % auth)
1217 self.wfile.write('pairs=%s<p>' % pairs)
1218 self.wfile.write('</body></html>')
1219
1220 return True
1221
1222 def SlowServerHandler(self):
1223 """Wait for the user suggested time before responding. The syntax is
1224 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001225 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001226 return False
1227 query_char = self.path.find('?')
1228 wait_sec = 1.0
1229 if query_char >= 0:
1230 try:
1231 wait_sec = int(self.path[query_char + 1:])
1232 except ValueError:
1233 pass
1234 time.sleep(wait_sec)
1235 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001236 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001237 self.end_headers()
1238 self.wfile.write("waited %d seconds" % wait_sec)
1239 return True
1240
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001241 def ChunkedServerHandler(self):
1242 """Send chunked response. Allows to specify chunks parameters:
1243 - waitBeforeHeaders - ms to wait before sending headers
1244 - waitBetweenChunks - ms to wait between chunks
1245 - chunkSize - size of each chunk in bytes
1246 - chunksNumber - number of chunks
1247 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1248 waits one second, then sends headers and five chunks five bytes each."""
1249 if not self._ShouldHandleRequest("/chunked"):
1250 return False
1251 query_char = self.path.find('?')
1252 chunkedSettings = {'waitBeforeHeaders' : 0,
1253 'waitBetweenChunks' : 0,
1254 'chunkSize' : 5,
1255 'chunksNumber' : 5}
1256 if query_char >= 0:
1257 params = self.path[query_char + 1:].split('&')
1258 for param in params:
1259 keyValue = param.split('=')
1260 if len(keyValue) == 2:
1261 try:
1262 chunkedSettings[keyValue[0]] = int(keyValue[1])
1263 except ValueError:
1264 pass
1265 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1266 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1267 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001268 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001269 self.send_header('Connection', 'close')
1270 self.send_header('Transfer-Encoding', 'chunked')
1271 self.end_headers()
1272 # Chunked encoding: sending all chunks, then final zero-length chunk and
1273 # then final CRLF.
1274 for i in range(0, chunkedSettings['chunksNumber']):
1275 if i > 0:
1276 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1277 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1278 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1279 self.sendChunkHelp('')
1280 return True
1281
initial.commit94958cf2008-07-26 22:42:52 +00001282 def ContentTypeHandler(self):
1283 """Returns a string of html with the given content type. E.g.,
1284 /contenttype?text/css returns an html file with the Content-Type
1285 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001286 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001287 return False
1288 query_char = self.path.find('?')
1289 content_type = self.path[query_char + 1:].strip()
1290 if not content_type:
1291 content_type = 'text/html'
1292 self.send_response(200)
1293 self.send_header('Content-Type', content_type)
1294 self.end_headers()
1295 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1296 return True
1297
creis@google.com2f4f6a42011-03-25 19:44:19 +00001298 def NoContentHandler(self):
1299 """Returns a 204 No Content response."""
1300 if not self._ShouldHandleRequest("/nocontent"):
1301 return False
1302 self.send_response(204)
1303 self.end_headers()
1304 return True
1305
initial.commit94958cf2008-07-26 22:42:52 +00001306 def ServerRedirectHandler(self):
1307 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001308 '/server-redirect?http://foo.bar/asdf' to redirect to
1309 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001310
1311 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001312 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001313 return False
1314
1315 query_char = self.path.find('?')
1316 if query_char < 0 or len(self.path) <= query_char + 1:
1317 self.sendRedirectHelp(test_name)
1318 return True
1319 dest = self.path[query_char + 1:]
1320
1321 self.send_response(301) # moved permanently
1322 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001323 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001324 self.end_headers()
1325 self.wfile.write('<html><head>')
1326 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1327
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001328 return True
initial.commit94958cf2008-07-26 22:42:52 +00001329
1330 def ClientRedirectHandler(self):
1331 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001332 '/client-redirect?http://foo.bar/asdf' to redirect to
1333 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001334
1335 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001336 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001337 return False
1338
1339 query_char = self.path.find('?');
1340 if query_char < 0 or len(self.path) <= query_char + 1:
1341 self.sendRedirectHelp(test_name)
1342 return True
1343 dest = self.path[query_char + 1:]
1344
1345 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001346 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001347 self.end_headers()
1348 self.wfile.write('<html><head>')
1349 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1350 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1351
1352 return True
1353
tony@chromium.org03266982010-03-05 03:18:42 +00001354 def MultipartHandler(self):
1355 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001356 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001357 if not self._ShouldHandleRequest(test_name):
1358 return False
1359
1360 num_frames = 10
1361 bound = '12345'
1362 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001363 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001364 'multipart/x-mixed-replace;boundary=' + bound)
1365 self.end_headers()
1366
1367 for i in xrange(num_frames):
1368 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001369 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001370 self.wfile.write('<title>page ' + str(i) + '</title>')
1371 self.wfile.write('page ' + str(i))
1372
1373 self.wfile.write('--' + bound + '--')
1374 return True
1375
tony@chromium.org4cb88302011-09-27 22:13:49 +00001376 def MultipartSlowHandler(self):
1377 """Send a multipart response (3 text/html pages) with a slight delay
1378 between each page. This is similar to how some pages show status using
1379 multipart."""
1380 test_name = '/multipart-slow'
1381 if not self._ShouldHandleRequest(test_name):
1382 return False
1383
1384 num_frames = 3
1385 bound = '12345'
1386 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001387 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001388 'multipart/x-mixed-replace;boundary=' + bound)
1389 self.end_headers()
1390
1391 for i in xrange(num_frames):
1392 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001393 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001394 time.sleep(0.25)
1395 if i == 2:
1396 self.wfile.write('<title>PASS</title>')
1397 else:
1398 self.wfile.write('<title>page ' + str(i) + '</title>')
1399 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1400
1401 self.wfile.write('--' + bound + '--')
1402 return True
1403
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001404 def GetSSLSessionCacheHandler(self):
1405 """Send a reply containing a log of the session cache operations."""
1406
1407 if not self._ShouldHandleRequest('/ssl-session-cache'):
1408 return False
1409
1410 self.send_response(200)
1411 self.send_header('Content-Type', 'text/plain')
1412 self.end_headers()
1413 try:
1414 for (action, sessionID) in self.server.session_cache.log:
1415 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1416 except AttributeError, e:
1417 self.wfile.write('Pass --https-record-resume in order to use' +
1418 ' this request')
1419 return True
1420
initial.commit94958cf2008-07-26 22:42:52 +00001421 def DefaultResponseHandler(self):
1422 """This is the catch-all response handler for requests that aren't handled
1423 by one of the special handlers above.
1424 Note that we specify the content-length as without it the https connection
1425 is not closed properly (and the browser keeps expecting data)."""
1426
1427 contents = "Default response given for path: " + self.path
1428 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001429 self.send_header('Content-Type', 'text/html')
1430 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001431 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001432 if (self.command != 'HEAD'):
1433 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001434 return True
1435
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001436 def RedirectConnectHandler(self):
1437 """Sends a redirect to the CONNECT request for www.redirect.com. This
1438 response is not specified by the RFC, so the browser should not follow
1439 the redirect."""
1440
1441 if (self.path.find("www.redirect.com") < 0):
1442 return False
1443
1444 dest = "http://www.destination.com/foo.js"
1445
1446 self.send_response(302) # moved temporarily
1447 self.send_header('Location', dest)
1448 self.send_header('Connection', 'close')
1449 self.end_headers()
1450 return True
1451
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001452 def ServerAuthConnectHandler(self):
1453 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1454 response doesn't make sense because the proxy server cannot request
1455 server authentication."""
1456
1457 if (self.path.find("www.server-auth.com") < 0):
1458 return False
1459
1460 challenge = 'Basic realm="WallyWorld"'
1461
1462 self.send_response(401) # unauthorized
1463 self.send_header('WWW-Authenticate', challenge)
1464 self.send_header('Connection', 'close')
1465 self.end_headers()
1466 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001467
1468 def DefaultConnectResponseHandler(self):
1469 """This is the catch-all response handler for CONNECT requests that aren't
1470 handled by one of the special handlers above. Real Web servers respond
1471 with 400 to CONNECT requests."""
1472
1473 contents = "Your client has issued a malformed or illegal request."
1474 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001475 self.send_header('Content-Type', 'text/html')
1476 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001477 self.end_headers()
1478 self.wfile.write(contents)
1479 return True
1480
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001481 def DeviceManagementHandler(self):
1482 """Delegates to the device management service used for cloud policy."""
1483 if not self._ShouldHandleRequest("/device_management"):
1484 return False
1485
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001486 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001487
1488 if not self.server._device_management_handler:
1489 import device_management
1490 policy_path = os.path.join(self.server.data_dir, 'device_management')
1491 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001492 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001493 self.server.policy_keys,
1494 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001495
1496 http_response, raw_reply = (
1497 self.server._device_management_handler.HandleRequest(self.path,
1498 self.headers,
1499 raw_request))
1500 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001501 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001502 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001503 self.end_headers()
1504 self.wfile.write(raw_reply)
1505 return True
1506
initial.commit94958cf2008-07-26 22:42:52 +00001507 # called by the redirect handling function when there is no parameter
1508 def sendRedirectHelp(self, redirect_name):
1509 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001510 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001511 self.end_headers()
1512 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1513 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1514 self.wfile.write('</body></html>')
1515
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001516 # called by chunked handling function
1517 def sendChunkHelp(self, chunk):
1518 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1519 self.wfile.write('%X\r\n' % len(chunk))
1520 self.wfile.write(chunk)
1521 self.wfile.write('\r\n')
1522
akalin@chromium.org154bb132010-11-12 02:20:27 +00001523
1524class SyncPageHandler(BasePageHandler):
1525 """Handler for the main HTTP sync server."""
1526
1527 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001528 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001529 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001530 self.ChromiumSyncDisableNotificationsOpHandler,
1531 self.ChromiumSyncEnableNotificationsOpHandler,
1532 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001533 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001534 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001535 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001536 self.ChromiumSyncErrorOpHandler,
1537 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001538
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001539 post_handlers = [self.ChromiumSyncCommandHandler,
1540 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001541 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001542 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001543 post_handlers, [])
1544
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001545
akalin@chromium.org154bb132010-11-12 02:20:27 +00001546 def ChromiumSyncTimeHandler(self):
1547 """Handle Chromium sync .../time requests.
1548
1549 The syncer sometimes checks server reachability by examining /time.
1550 """
1551 test_name = "/chromiumsync/time"
1552 if not self._ShouldHandleRequest(test_name):
1553 return False
1554
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001555 # Chrome hates it if we send a response before reading the request.
1556 if self.headers.getheader('content-length'):
1557 length = int(self.headers.getheader('content-length'))
1558 raw_request = self.rfile.read(length)
1559
akalin@chromium.org154bb132010-11-12 02:20:27 +00001560 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001561 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001562 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001563 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001564 return True
1565
1566 def ChromiumSyncCommandHandler(self):
1567 """Handle a chromiumsync command arriving via http.
1568
1569 This covers all sync protocol commands: authentication, getupdates, and
1570 commit.
1571 """
1572 test_name = "/chromiumsync/command"
1573 if not self._ShouldHandleRequest(test_name):
1574 return False
1575
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001576 length = int(self.headers.getheader('content-length'))
1577 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001578 http_response = 200
1579 raw_reply = None
1580 if not self.server.GetAuthenticated():
1581 http_response = 401
1582 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1583 else:
1584 http_response, raw_reply = self.server.HandleCommand(
1585 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001586
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001587 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001588 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001589 if http_response == 401:
1590 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001591 self.end_headers()
1592 self.wfile.write(raw_reply)
1593 return True
1594
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001595 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001596 test_name = "/chromiumsync/migrate"
1597 if not self._ShouldHandleRequest(test_name):
1598 return False
1599
1600 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1601 self.path)
1602 self.send_response(http_response)
1603 self.send_header('Content-Type', 'text/html')
1604 self.send_header('Content-Length', len(raw_reply))
1605 self.end_headers()
1606 self.wfile.write(raw_reply)
1607 return True
1608
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001609 def ChromiumSyncCredHandler(self):
1610 test_name = "/chromiumsync/cred"
1611 if not self._ShouldHandleRequest(test_name):
1612 return False
1613 try:
1614 query = urlparse.urlparse(self.path)[4]
1615 cred_valid = urlparse.parse_qs(query)['valid']
1616 if cred_valid[0] == 'True':
1617 self.server.SetAuthenticated(True)
1618 else:
1619 self.server.SetAuthenticated(False)
1620 except:
1621 self.server.SetAuthenticated(False)
1622
1623 http_response = 200
1624 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1625 self.send_response(http_response)
1626 self.send_header('Content-Type', 'text/html')
1627 self.send_header('Content-Length', len(raw_reply))
1628 self.end_headers()
1629 self.wfile.write(raw_reply)
1630 return True
1631
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001632 def ChromiumSyncDisableNotificationsOpHandler(self):
1633 test_name = "/chromiumsync/disablenotifications"
1634 if not self._ShouldHandleRequest(test_name):
1635 return False
1636 self.server.GetXmppServer().DisableNotifications()
1637 result = 200
1638 raw_reply = ('<html><title>Notifications disabled</title>'
1639 '<H1>Notifications disabled</H1></html>')
1640 self.send_response(result)
1641 self.send_header('Content-Type', 'text/html')
1642 self.send_header('Content-Length', len(raw_reply))
1643 self.end_headers()
1644 self.wfile.write(raw_reply)
1645 return True;
1646
1647 def ChromiumSyncEnableNotificationsOpHandler(self):
1648 test_name = "/chromiumsync/enablenotifications"
1649 if not self._ShouldHandleRequest(test_name):
1650 return False
1651 self.server.GetXmppServer().EnableNotifications()
1652 result = 200
1653 raw_reply = ('<html><title>Notifications enabled</title>'
1654 '<H1>Notifications enabled</H1></html>')
1655 self.send_response(result)
1656 self.send_header('Content-Type', 'text/html')
1657 self.send_header('Content-Length', len(raw_reply))
1658 self.end_headers()
1659 self.wfile.write(raw_reply)
1660 return True;
1661
1662 def ChromiumSyncSendNotificationOpHandler(self):
1663 test_name = "/chromiumsync/sendnotification"
1664 if not self._ShouldHandleRequest(test_name):
1665 return False
1666 query = urlparse.urlparse(self.path)[4]
1667 query_params = urlparse.parse_qs(query)
1668 channel = ''
1669 data = ''
1670 if 'channel' in query_params:
1671 channel = query_params['channel'][0]
1672 if 'data' in query_params:
1673 data = query_params['data'][0]
1674 self.server.GetXmppServer().SendNotification(channel, data)
1675 result = 200
1676 raw_reply = ('<html><title>Notification sent</title>'
1677 '<H1>Notification sent with channel "%s" '
1678 'and data "%s"</H1></html>'
1679 % (channel, data))
1680 self.send_response(result)
1681 self.send_header('Content-Type', 'text/html')
1682 self.send_header('Content-Length', len(raw_reply))
1683 self.end_headers()
1684 self.wfile.write(raw_reply)
1685 return True;
1686
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001687 def ChromiumSyncBirthdayErrorOpHandler(self):
1688 test_name = "/chromiumsync/birthdayerror"
1689 if not self._ShouldHandleRequest(test_name):
1690 return False
1691 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1692 self.send_response(result)
1693 self.send_header('Content-Type', 'text/html')
1694 self.send_header('Content-Length', len(raw_reply))
1695 self.end_headers()
1696 self.wfile.write(raw_reply)
1697 return True;
1698
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001699 def ChromiumSyncTransientErrorOpHandler(self):
1700 test_name = "/chromiumsync/transienterror"
1701 if not self._ShouldHandleRequest(test_name):
1702 return False
1703 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1704 self.send_response(result)
1705 self.send_header('Content-Type', 'text/html')
1706 self.send_header('Content-Length', len(raw_reply))
1707 self.end_headers()
1708 self.wfile.write(raw_reply)
1709 return True;
1710
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001711 def ChromiumSyncErrorOpHandler(self):
1712 test_name = "/chromiumsync/error"
1713 if not self._ShouldHandleRequest(test_name):
1714 return False
1715 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1716 self.path)
1717 self.send_response(result)
1718 self.send_header('Content-Type', 'text/html')
1719 self.send_header('Content-Length', len(raw_reply))
1720 self.end_headers()
1721 self.wfile.write(raw_reply)
1722 return True;
1723
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001724 def ChromiumSyncSyncTabsOpHandler(self):
1725 test_name = "/chromiumsync/synctabs"
1726 if not self._ShouldHandleRequest(test_name):
1727 return False
1728 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1729 self.send_response(result)
1730 self.send_header('Content-Type', 'text/html')
1731 self.send_header('Content-Length', len(raw_reply))
1732 self.end_headers()
1733 self.wfile.write(raw_reply)
1734 return True;
1735
akalin@chromium.org154bb132010-11-12 02:20:27 +00001736
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001737def MakeDataDir():
1738 if options.data_dir:
1739 if not os.path.isdir(options.data_dir):
1740 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1741 return None
1742 my_data_dir = options.data_dir
1743 else:
1744 # Create the default path to our data dir, relative to the exe dir.
1745 my_data_dir = os.path.dirname(sys.argv[0])
1746 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001747 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001748
1749 #TODO(ibrar): Must use Find* funtion defined in google\tools
1750 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1751
1752 return my_data_dir
1753
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001754
1755class TCPEchoHandler(SocketServer.BaseRequestHandler):
1756 """The RequestHandler class for TCP echo server.
1757
1758 It is instantiated once per connection to the server, and overrides the
1759 handle() method to implement communication to the client.
1760 """
1761
1762 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001763 """Handles the request from the client and constructs a response."""
1764
1765 data = self.request.recv(65536).strip()
1766 # Verify the "echo request" message received from the client. Send back
1767 # "echo response" message if "echo request" message is valid.
1768 try:
1769 return_data = echo_message.GetEchoResponseData(data)
1770 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001771 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001772 except ValueError:
1773 return
1774
1775 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001776
1777
1778class UDPEchoHandler(SocketServer.BaseRequestHandler):
1779 """The RequestHandler class for UDP echo server.
1780
1781 It is instantiated once per connection to the server, and overrides the
1782 handle() method to implement communication to the client.
1783 """
1784
1785 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001786 """Handles the request from the client and constructs a response."""
1787
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001788 data = self.request[0].strip()
1789 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001790 # Verify the "echo request" message received from the client. Send back
1791 # "echo response" message if "echo request" message is valid.
1792 try:
1793 return_data = echo_message.GetEchoResponseData(data)
1794 if not return_data:
1795 return
1796 except ValueError:
1797 return
1798 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001799
1800
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001801class FileMultiplexer:
1802 def __init__(self, fd1, fd2) :
1803 self.__fd1 = fd1
1804 self.__fd2 = fd2
1805
1806 def __del__(self) :
1807 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1808 self.__fd1.close()
1809 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1810 self.__fd2.close()
1811
1812 def write(self, text) :
1813 self.__fd1.write(text)
1814 self.__fd2.write(text)
1815
1816 def flush(self) :
1817 self.__fd1.flush()
1818 self.__fd2.flush()
1819
initial.commit94958cf2008-07-26 22:42:52 +00001820def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001821 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001822 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001823 if options.log_to_console:
1824 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1825 else:
1826 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001827
1828 port = options.port
1829
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001830 server_data = {}
1831
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001832 if options.server_type == SERVER_HTTP:
1833 if options.cert:
1834 # let's make sure the cert file exists.
1835 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001836 print 'specified server cert file not found: ' + options.cert + \
1837 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001838 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001839 for ca_cert in options.ssl_client_ca:
1840 if not os.path.isfile(ca_cert):
1841 print 'specified trusted client CA file not found: ' + ca_cert + \
1842 ' exiting...'
1843 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001844 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001845 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001846 options.ssl_bulk_cipher, options.record_resume)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001847 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001848 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001849 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001850 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001851
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001852 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001853 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001854 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001855 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001856 server.policy_keys = options.policy_keys
1857 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001858 elif options.server_type == SERVER_SYNC:
1859 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1860 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001861 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1862 server_data['port'] = server.server_port
1863 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001864 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001865 # Used for generating the key (randomly) that encodes the "echo request"
1866 # message.
1867 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001868 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1869 print 'Echo TCP server started on port %d...' % server.server_port
1870 server_data['port'] = server.server_port
1871 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001872 # Used for generating the key (randomly) that encodes the "echo request"
1873 # message.
1874 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001875 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1876 print 'Echo UDP server started on port %d...' % server.server_port
1877 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001878 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001879 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001880 my_data_dir = MakeDataDir()
1881
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001882 # Instantiate a dummy authorizer for managing 'virtual' users
1883 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1884
1885 # Define a new user having full r/w permissions and a read-only
1886 # anonymous user
1887 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1888
1889 authorizer.add_anonymous(my_data_dir)
1890
1891 # Instantiate FTP handler class
1892 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1893 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001894
1895 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001896 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1897 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001898
1899 # Instantiate FTP server class and listen to 127.0.0.1:port
1900 address = ('127.0.0.1', port)
1901 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001902 server_data['port'] = server.socket.getsockname()[1]
1903 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001904
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001905 # Notify the parent that we've started. (BaseServer subclasses
1906 # bind their sockets on construction.)
1907 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001908 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001909 server_data_len = len(server_data_json)
1910 print 'sending server_data: %s (%d bytes)' % (
1911 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001912 if sys.platform == 'win32':
1913 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1914 else:
1915 fd = options.startup_pipe
1916 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001917 # First write the data length as an unsigned 4-byte value. This
1918 # is _not_ using network byte ordering since the other end of the
1919 # pipe is on the same machine.
1920 startup_pipe.write(struct.pack('=L', server_data_len))
1921 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001922 startup_pipe.close()
1923
initial.commit94958cf2008-07-26 22:42:52 +00001924 try:
1925 server.serve_forever()
1926 except KeyboardInterrupt:
1927 print 'shutting down server'
1928 server.stop = True
1929
1930if __name__ == '__main__':
1931 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001932 option_parser.add_option("-f", '--ftp', action='store_const',
1933 const=SERVER_FTP, default=SERVER_HTTP,
1934 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001935 help='start up an FTP server.')
1936 option_parser.add_option('', '--sync', action='store_const',
1937 const=SERVER_SYNC, default=SERVER_HTTP,
1938 dest='server_type',
1939 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001940 option_parser.add_option('', '--tcp-echo', action='store_const',
1941 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1942 dest='server_type',
1943 help='start up a tcp echo server.')
1944 option_parser.add_option('', '--udp-echo', action='store_const',
1945 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1946 dest='server_type',
1947 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001948 option_parser.add_option('', '--log-to-console', action='store_const',
1949 const=True, default=False,
1950 dest='log_to_console',
1951 help='Enables or disables sys.stdout logging to '
1952 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001953 option_parser.add_option('', '--port', default='0', type='int',
1954 help='Port used by the server. If unspecified, the '
1955 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001956 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001957 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001958 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001959 help='Specify that https should be used, specify '
1960 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001961 'the server should use.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001962 option_parser.add_option('', '--https-record-resume', dest='record_resume',
1963 const=True, default=False, action='store_const',
1964 help='Record resumption cache events rather than'
1965 ' resuming as normal. Allows the use of the'
1966 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001967 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1968 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001969 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1970 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001971 'should include the CA named in the subject of '
1972 'the DER-encoded certificate contained in the '
1973 'specified file. This option may appear multiple '
1974 'times, indicating multiple CA names should be '
1975 'sent in the request.')
1976 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1977 help='Specify the bulk encryption algorithm(s)'
1978 'that will be accepted by the SSL server. Valid '
1979 'values are "aes256", "aes128", "3des", "rc4". If '
1980 'omitted, all algorithms will be used. This '
1981 'option may appear multiple times, indicating '
1982 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001983 option_parser.add_option('', '--file-root-url', default='/files/',
1984 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001985 option_parser.add_option('', '--startup-pipe', type='int',
1986 dest='startup_pipe',
1987 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001988 option_parser.add_option('', '--policy-key', action='append',
1989 dest='policy_keys',
1990 help='Specify a path to a PEM-encoded private key '
1991 'to use for policy signing. May be specified '
1992 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001993 'the server. If ther server has multiple keys, it '
1994 'will rotate through them in at each request a '
1995 'round-robin fashion. The server will generate a '
1996 'random key if none is specified on the command '
1997 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001998 option_parser.add_option('', '--policy-user', default='user@example.com',
1999 dest='policy_user',
2000 help='Specify the user name the server should '
2001 'report back to the client as the user owning the '
2002 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00002003 options, args = option_parser.parse_args()
2004
2005 sys.exit(main(options, args))