blob: f280333473a327c6046e966279cc7207218c6b0f [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
initial.commit94958cf2008-07-26 22:42:52 +000021import optparse
22import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000023import random
initial.commit94958cf2008-07-26 22:42:52 +000024import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000025import select
initial.commit94958cf2008-07-26 22:42:52 +000026import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import socket
initial.commit94958cf2008-07-26 22:42:52 +000028import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000029import struct
initial.commit94958cf2008-07-26 22:42:52 +000030import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000031import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000032import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000033import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000034import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000035
36# Ignore deprecation warnings, they make our output more cluttered.
37warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000039import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000040import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000041import tlslite
42import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000044try:
45 import hashlib
46 _new_md5 = hashlib.md5
47except ImportError:
48 import md5
49 _new_md5 = md5.new
50
dpranke@chromium.org70049b72011-10-14 00:38:18 +000051try:
52 import json
53except ImportError:
54 import simplejson as json
55
davidben@chromium.org06fcf202010-09-22 18:15:23 +000056if sys.platform == 'win32':
57 import msvcrt
58
maruel@chromium.org756cf982009-03-05 12:46:38 +000059SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000060SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000061SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000062SERVER_TCP_ECHO = 3
63SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000064
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000065# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000066debug_output = sys.stderr
67def debug(str):
68 debug_output.write(str + "\n")
69 debug_output.flush()
70
agl@chromium.orgf9e66792011-12-12 22:22:19 +000071class RecordingSSLSessionCache(object):
72 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
73 lookups and inserts in order to test session cache behaviours."""
74
75 def __init__(self):
76 self.log = []
77
78 def __getitem__(self, sessionID):
79 self.log.append(('lookup', sessionID))
80 raise KeyError()
81
82 def __setitem__(self, sessionID, session):
83 self.log.append(('insert', sessionID))
84
initial.commit94958cf2008-07-26 22:42:52 +000085class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
86 """This is a specialization of of BaseHTTPServer to allow it
87 to be exited cleanly (by setting its "stop" member to True)."""
88
89 def serve_forever(self):
90 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000091 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000092 while not self.stop:
93 self.handle_request()
94 self.socket.close()
95
96class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
97 """This is a specialization of StoppableHTTPerver that add https support."""
98
davidben@chromium.org31282a12010-08-07 01:10:02 +000099 def __init__(self, server_address, request_hander_class, cert_path,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000100 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
101 record_resume_info):
initial.commit94958cf2008-07-26 22:42:52 +0000102 s = open(cert_path).read()
103 x509 = tlslite.api.X509()
104 x509.parse(s)
105 self.cert_chain = tlslite.api.X509CertChain([x509])
106 s = open(cert_path).read()
107 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000108 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000109 self.ssl_client_cas = []
110 for ca_file in ssl_client_cas:
111 s = open(ca_file).read()
112 x509 = tlslite.api.X509()
113 x509.parse(s)
114 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000115 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
116 if ssl_bulk_ciphers is not None:
117 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000118
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000119 if record_resume_info:
120 # If record_resume_info is true then we'll replace the session cache with
121 # an object that records the lookups and inserts that it sees.
122 self.session_cache = RecordingSSLSessionCache()
123 else:
124 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000125 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
126
127 def handshake(self, tlsConnection):
128 """Creates the SSL connection."""
129 try:
130 tlsConnection.handshakeServer(certChain=self.cert_chain,
131 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000132 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000133 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000134 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000135 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000136 tlsConnection.ignoreAbruptClose = True
137 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000138 except tlslite.api.TLSAbruptCloseError:
139 # Ignore abrupt close.
140 return True
initial.commit94958cf2008-07-26 22:42:52 +0000141 except tlslite.api.TLSError, error:
142 print "Handshake failure:", str(error)
143 return False
144
akalin@chromium.org154bb132010-11-12 02:20:27 +0000145
146class SyncHTTPServer(StoppableHTTPServer):
147 """An HTTP server that handles sync commands."""
148
149 def __init__(self, server_address, request_handler_class):
150 # We import here to avoid pulling in chromiumsync's dependencies
151 # unless strictly necessary.
152 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000153 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000154 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000155 self._sync_handler = chromiumsync.TestServer()
156 self._xmpp_socket_map = {}
157 self._xmpp_server = xmppserver.XmppServer(
158 self._xmpp_socket_map, ('localhost', 0))
159 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000160 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000161
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000162 def GetXmppServer(self):
163 return self._xmpp_server
164
akalin@chromium.org154bb132010-11-12 02:20:27 +0000165 def HandleCommand(self, query, raw_request):
166 return self._sync_handler.HandleCommand(query, raw_request)
167
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000168 def HandleRequestNoBlock(self):
169 """Handles a single request.
170
171 Copied from SocketServer._handle_request_noblock().
172 """
173 try:
174 request, client_address = self.get_request()
175 except socket.error:
176 return
177 if self.verify_request(request, client_address):
178 try:
179 self.process_request(request, client_address)
180 except:
181 self.handle_error(request, client_address)
182 self.close_request(request)
183
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000184 def SetAuthenticated(self, auth_valid):
185 self.authenticated = auth_valid
186
187 def GetAuthenticated(self):
188 return self.authenticated
189
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000190 def serve_forever(self):
191 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
192 """
193
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000194 def HandleXmppSocket(fd, socket_map, handler):
195 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000196
197 Adapted from asyncore.read() et al.
198 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000199 xmpp_connection = socket_map.get(fd)
200 # This could happen if a previous handler call caused fd to get
201 # removed from socket_map.
202 if xmpp_connection is None:
203 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000204 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000205 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000206 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
207 raise
208 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000209 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000210
211 while True:
212 read_fds = [ self.fileno() ]
213 write_fds = []
214 exceptional_fds = []
215
216 for fd, xmpp_connection in self._xmpp_socket_map.items():
217 is_r = xmpp_connection.readable()
218 is_w = xmpp_connection.writable()
219 if is_r:
220 read_fds.append(fd)
221 if is_w:
222 write_fds.append(fd)
223 if is_r or is_w:
224 exceptional_fds.append(fd)
225
226 try:
227 read_fds, write_fds, exceptional_fds = (
228 select.select(read_fds, write_fds, exceptional_fds))
229 except select.error, err:
230 if err.args[0] != errno.EINTR:
231 raise
232 else:
233 continue
234
235 for fd in read_fds:
236 if fd == self.fileno():
237 self.HandleRequestNoBlock()
238 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000239 HandleXmppSocket(fd, self._xmpp_socket_map,
240 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000241
242 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000243 HandleXmppSocket(fd, self._xmpp_socket_map,
244 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000245
246 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000247 HandleXmppSocket(fd, self._xmpp_socket_map,
248 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000249
akalin@chromium.org154bb132010-11-12 02:20:27 +0000250
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000251class TCPEchoServer(SocketServer.TCPServer):
252 """A TCP echo server that echoes back what it has received."""
253
254 def server_bind(self):
255 """Override server_bind to store the server name."""
256 SocketServer.TCPServer.server_bind(self)
257 host, port = self.socket.getsockname()[:2]
258 self.server_name = socket.getfqdn(host)
259 self.server_port = port
260
261 def serve_forever(self):
262 self.stop = False
263 self.nonce_time = None
264 while not self.stop:
265 self.handle_request()
266 self.socket.close()
267
268
269class UDPEchoServer(SocketServer.UDPServer):
270 """A UDP echo server that echoes back what it has received."""
271
272 def server_bind(self):
273 """Override server_bind to store the server name."""
274 SocketServer.UDPServer.server_bind(self)
275 host, port = self.socket.getsockname()[:2]
276 self.server_name = socket.getfqdn(host)
277 self.server_port = port
278
279 def serve_forever(self):
280 self.stop = False
281 self.nonce_time = None
282 while not self.stop:
283 self.handle_request()
284 self.socket.close()
285
286
akalin@chromium.org154bb132010-11-12 02:20:27 +0000287class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
288
289 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000290 connect_handlers, get_handlers, head_handlers, post_handlers,
291 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000292 self._connect_handlers = connect_handlers
293 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000294 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000295 self._post_handlers = post_handlers
296 self._put_handlers = put_handlers
297 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
298 self, request, client_address, socket_server)
299
300 def log_request(self, *args, **kwargs):
301 # Disable request logging to declutter test log output.
302 pass
303
304 def _ShouldHandleRequest(self, handler_name):
305 """Determines if the path can be handled by the handler.
306
307 We consider a handler valid if the path begins with the
308 handler name. It can optionally be followed by "?*", "/*".
309 """
310
311 pattern = re.compile('%s($|\?|/).*' % handler_name)
312 return pattern.match(self.path)
313
314 def do_CONNECT(self):
315 for handler in self._connect_handlers:
316 if handler():
317 return
318
319 def do_GET(self):
320 for handler in self._get_handlers:
321 if handler():
322 return
323
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000324 def do_HEAD(self):
325 for handler in self._head_handlers:
326 if handler():
327 return
328
akalin@chromium.org154bb132010-11-12 02:20:27 +0000329 def do_POST(self):
330 for handler in self._post_handlers:
331 if handler():
332 return
333
334 def do_PUT(self):
335 for handler in self._put_handlers:
336 if handler():
337 return
338
339
340class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000341
342 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000343 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000344 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000345 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000346 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000347 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000348 self.NoCacheMaxAgeTimeHandler,
349 self.NoCacheTimeHandler,
350 self.CacheTimeHandler,
351 self.CacheExpiresHandler,
352 self.CacheProxyRevalidateHandler,
353 self.CachePrivateHandler,
354 self.CachePublicHandler,
355 self.CacheSMaxAgeHandler,
356 self.CacheMustRevalidateHandler,
357 self.CacheMustRevalidateMaxAgeHandler,
358 self.CacheNoStoreHandler,
359 self.CacheNoStoreMaxAgeHandler,
360 self.CacheNoTransformHandler,
361 self.DownloadHandler,
362 self.DownloadFinishHandler,
363 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000364 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000365 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000366 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000367 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000368 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000369 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000370 self.AuthBasicHandler,
371 self.AuthDigestHandler,
372 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000373 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000374 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000375 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000376 self.ServerRedirectHandler,
377 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000378 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000379 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000380 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000381 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000382 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000383 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000384 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000385 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000386 self.DeviceManagementHandler,
387 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000388 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000389 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000390 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000391 head_handlers = [
392 self.FileHandler,
393 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000394
maruel@google.come250a9b2009-03-10 17:39:46 +0000395 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000396 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000397 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000398 'gif': 'image/gif',
399 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000400 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000401 'pdf' : 'application/pdf',
402 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000403 }
initial.commit94958cf2008-07-26 22:42:52 +0000404 self._default_mime_type = 'text/html'
405
akalin@chromium.org154bb132010-11-12 02:20:27 +0000406 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000407 connect_handlers, get_handlers, head_handlers,
408 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000409
initial.commit94958cf2008-07-26 22:42:52 +0000410 def GetMIMETypeFromName(self, file_name):
411 """Returns the mime type for the specified file_name. So far it only looks
412 at the file extension."""
413
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000414 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000415 if len(extension) == 0:
416 # no extension.
417 return self._default_mime_type
418
ericroman@google.comc17ca532009-05-07 03:51:05 +0000419 # extension starts with a dot, so we need to remove it
420 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000421
initial.commit94958cf2008-07-26 22:42:52 +0000422 def NoCacheMaxAgeTimeHandler(self):
423 """This request handler yields a page with the title set to the current
424 system time, and no caching requested."""
425
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000426 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000427 return False
428
429 self.send_response(200)
430 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000431 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000432 self.end_headers()
433
maruel@google.come250a9b2009-03-10 17:39:46 +0000434 self.wfile.write('<html><head><title>%s</title></head></html>' %
435 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000436
437 return True
438
439 def NoCacheTimeHandler(self):
440 """This request handler yields a page with the title set to the current
441 system time, and no caching requested."""
442
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000443 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000444 return False
445
446 self.send_response(200)
447 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000448 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000449 self.end_headers()
450
maruel@google.come250a9b2009-03-10 17:39:46 +0000451 self.wfile.write('<html><head><title>%s</title></head></html>' %
452 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000453
454 return True
455
456 def CacheTimeHandler(self):
457 """This request handler yields a page with the title set to the current
458 system time, and allows caching for one minute."""
459
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000460 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000461 return False
462
463 self.send_response(200)
464 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000465 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000466 self.end_headers()
467
maruel@google.come250a9b2009-03-10 17:39:46 +0000468 self.wfile.write('<html><head><title>%s</title></head></html>' %
469 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000470
471 return True
472
473 def CacheExpiresHandler(self):
474 """This request handler yields a page with the title set to the current
475 system time, and set the page to expire on 1 Jan 2099."""
476
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000477 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000478 return False
479
480 self.send_response(200)
481 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000482 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000483 self.end_headers()
484
maruel@google.come250a9b2009-03-10 17:39:46 +0000485 self.wfile.write('<html><head><title>%s</title></head></html>' %
486 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000487
488 return True
489
490 def CacheProxyRevalidateHandler(self):
491 """This request handler yields a page with the title set to the current
492 system time, and allows caching for 60 seconds"""
493
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000494 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000495 return False
496
497 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000498 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000499 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
500 self.end_headers()
501
maruel@google.come250a9b2009-03-10 17:39:46 +0000502 self.wfile.write('<html><head><title>%s</title></head></html>' %
503 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000504
505 return True
506
507 def CachePrivateHandler(self):
508 """This request handler yields a page with the title set to the current
509 system time, and allows caching for 5 seconds."""
510
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000511 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000512 return False
513
514 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000515 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000516 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000517 self.end_headers()
518
maruel@google.come250a9b2009-03-10 17:39:46 +0000519 self.wfile.write('<html><head><title>%s</title></head></html>' %
520 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000521
522 return True
523
524 def CachePublicHandler(self):
525 """This request handler yields a page with the title set to the current
526 system time, and allows caching for 5 seconds."""
527
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000528 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000529 return False
530
531 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000532 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000533 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000534 self.end_headers()
535
maruel@google.come250a9b2009-03-10 17:39:46 +0000536 self.wfile.write('<html><head><title>%s</title></head></html>' %
537 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000538
539 return True
540
541 def CacheSMaxAgeHandler(self):
542 """This request handler yields a page with the title set to the current
543 system time, and does not allow for caching."""
544
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000545 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000546 return False
547
548 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000549 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000550 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
551 self.end_headers()
552
maruel@google.come250a9b2009-03-10 17:39:46 +0000553 self.wfile.write('<html><head><title>%s</title></head></html>' %
554 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000555
556 return True
557
558 def CacheMustRevalidateHandler(self):
559 """This request handler yields a page with the title set to the current
560 system time, and does not allow caching."""
561
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000562 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000563 return False
564
565 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000566 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000567 self.send_header('Cache-Control', 'must-revalidate')
568 self.end_headers()
569
maruel@google.come250a9b2009-03-10 17:39:46 +0000570 self.wfile.write('<html><head><title>%s</title></head></html>' %
571 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000572
573 return True
574
575 def CacheMustRevalidateMaxAgeHandler(self):
576 """This request handler yields a page with the title set to the current
577 system time, and does not allow caching event though max-age of 60
578 seconds is specified."""
579
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000580 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000581 return False
582
583 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000584 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000585 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
586 self.end_headers()
587
maruel@google.come250a9b2009-03-10 17:39:46 +0000588 self.wfile.write('<html><head><title>%s</title></head></html>' %
589 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000590
591 return True
592
initial.commit94958cf2008-07-26 22:42:52 +0000593 def CacheNoStoreHandler(self):
594 """This request handler yields a page with the title set to the current
595 system time, and does not allow the page to be stored."""
596
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000597 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000598 return False
599
600 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000601 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000602 self.send_header('Cache-Control', 'no-store')
603 self.end_headers()
604
maruel@google.come250a9b2009-03-10 17:39:46 +0000605 self.wfile.write('<html><head><title>%s</title></head></html>' %
606 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000607
608 return True
609
610 def CacheNoStoreMaxAgeHandler(self):
611 """This request handler yields a page with the title set to the current
612 system time, and does not allow the page to be stored even though max-age
613 of 60 seconds is specified."""
614
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000615 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000616 return False
617
618 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000619 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000620 self.send_header('Cache-Control', 'max-age=60, no-store')
621 self.end_headers()
622
maruel@google.come250a9b2009-03-10 17:39:46 +0000623 self.wfile.write('<html><head><title>%s</title></head></html>' %
624 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000625
626 return True
627
628
629 def CacheNoTransformHandler(self):
630 """This request handler yields a page with the title set to the current
631 system time, and does not allow the content to transformed during
632 user-agent caching"""
633
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000634 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000635 return False
636
637 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000638 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000639 self.send_header('Cache-Control', 'no-transform')
640 self.end_headers()
641
maruel@google.come250a9b2009-03-10 17:39:46 +0000642 self.wfile.write('<html><head><title>%s</title></head></html>' %
643 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000644
645 return True
646
647 def EchoHeader(self):
648 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000649 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000650
ananta@chromium.org56812d02011-04-07 17:52:05 +0000651 """This function echoes back the value of a specific request header"""
652 """while allowing caching for 16 hours."""
653 def EchoHeaderCache(self):
654 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000655
656 def EchoHeaderHelper(self, echo_header):
657 """This function echoes back the value of the request header passed in."""
658 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000659 return False
660
661 query_char = self.path.find('?')
662 if query_char != -1:
663 header_name = self.path[query_char+1:]
664
665 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000666 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000667 if echo_header == '/echoheadercache':
668 self.send_header('Cache-control', 'max-age=60000')
669 else:
670 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000671 # insert a vary header to properly indicate that the cachability of this
672 # request is subject to value of the request header being echoed.
673 if len(header_name) > 0:
674 self.send_header('Vary', header_name)
675 self.end_headers()
676
677 if len(header_name) > 0:
678 self.wfile.write(self.headers.getheader(header_name))
679
680 return True
681
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000682 def ReadRequestBody(self):
683 """This function reads the body of the current HTTP request, handling
684 both plain and chunked transfer encoded requests."""
685
686 if self.headers.getheader('transfer-encoding') != 'chunked':
687 length = int(self.headers.getheader('content-length'))
688 return self.rfile.read(length)
689
690 # Read the request body as chunks.
691 body = ""
692 while True:
693 line = self.rfile.readline()
694 length = int(line, 16)
695 if length == 0:
696 self.rfile.readline()
697 break
698 body += self.rfile.read(length)
699 self.rfile.read(2)
700 return body
701
initial.commit94958cf2008-07-26 22:42:52 +0000702 def EchoHandler(self):
703 """This handler just echoes back the payload of the request, for testing
704 form submission."""
705
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000706 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000707 return False
708
709 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000710 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000711 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000712 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000713 return True
714
715 def EchoTitleHandler(self):
716 """This handler is like Echo, but sets the page title to the request."""
717
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000718 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000719 return False
720
721 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000722 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000723 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000724 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000725 self.wfile.write('<html><head><title>')
726 self.wfile.write(request)
727 self.wfile.write('</title></head></html>')
728 return True
729
730 def EchoAllHandler(self):
731 """This handler yields a (more) human-readable page listing information
732 about the request header & contents."""
733
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000734 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000735 return False
736
737 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000738 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000739 self.end_headers()
740 self.wfile.write('<html><head><style>'
741 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
742 '</style></head><body>'
743 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000744 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000745 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000746
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000747 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000748 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000749 params = cgi.parse_qs(qs, keep_blank_values=1)
750
751 for param in params:
752 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000753
754 self.wfile.write('</pre>')
755
756 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
757
758 self.wfile.write('</body></html>')
759 return True
760
761 def DownloadHandler(self):
762 """This handler sends a downloadable file with or without reporting
763 the size (6K)."""
764
765 if self.path.startswith("/download-unknown-size"):
766 send_length = False
767 elif self.path.startswith("/download-known-size"):
768 send_length = True
769 else:
770 return False
771
772 #
773 # The test which uses this functionality is attempting to send
774 # small chunks of data to the client. Use a fairly large buffer
775 # so that we'll fill chrome's IO buffer enough to force it to
776 # actually write the data.
777 # See also the comments in the client-side of this test in
778 # download_uitest.cc
779 #
780 size_chunk1 = 35*1024
781 size_chunk2 = 10*1024
782
783 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000784 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000785 self.send_header('Cache-Control', 'max-age=0')
786 if send_length:
787 self.send_header('Content-Length', size_chunk1 + size_chunk2)
788 self.end_headers()
789
790 # First chunk of data:
791 self.wfile.write("*" * size_chunk1)
792 self.wfile.flush()
793
794 # handle requests until one of them clears this flag.
795 self.server.waitForDownload = True
796 while self.server.waitForDownload:
797 self.server.handle_request()
798
799 # Second chunk of data:
800 self.wfile.write("*" * size_chunk2)
801 return True
802
803 def DownloadFinishHandler(self):
804 """This handler just tells the server to finish the current download."""
805
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000806 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000807 return False
808
809 self.server.waitForDownload = False
810 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000811 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000812 self.send_header('Cache-Control', 'max-age=0')
813 self.end_headers()
814 return True
815
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000816 def _ReplaceFileData(self, data, query_parameters):
817 """Replaces matching substrings in a file.
818
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000819 If the 'replace_text' URL query parameter is present, it is expected to be
820 of the form old_text:new_text, which indicates that any old_text strings in
821 the file are replaced with new_text. Multiple 'replace_text' parameters may
822 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000823
824 If the parameters are not present, |data| is returned.
825 """
826 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000827 replace_text_values = query_dict.get('replace_text', [])
828 for replace_text_value in replace_text_values:
829 replace_text_args = replace_text_value.split(':')
830 if len(replace_text_args) != 2:
831 raise ValueError(
832 'replace_text must be of form old_text:new_text. Actual value: %s' %
833 replace_text_value)
834 old_text_b64, new_text_b64 = replace_text_args
835 old_text = base64.urlsafe_b64decode(old_text_b64)
836 new_text = base64.urlsafe_b64decode(new_text_b64)
837 data = data.replace(old_text, new_text)
838 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000839
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000840 def ZipFileHandler(self):
841 """This handler sends the contents of the requested file in compressed form.
842 Can pass in a parameter that specifies that the content length be
843 C - the compressed size (OK),
844 U - the uncompressed size (Non-standard, but handled),
845 S - less than compressed (OK because we keep going),
846 M - larger than compressed but less than uncompressed (an error),
847 L - larger than uncompressed (an error)
848 Example: compressedfiles/Picture_1.doc?C
849 """
850
851 prefix = "/compressedfiles/"
852 if not self.path.startswith(prefix):
853 return False
854
855 # Consume a request body if present.
856 if self.command == 'POST' or self.command == 'PUT' :
857 self.ReadRequestBody()
858
859 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
860
861 if not query in ('C', 'U', 'S', 'M', 'L'):
862 return False
863
864 sub_path = url_path[len(prefix):]
865 entries = sub_path.split('/')
866 file_path = os.path.join(self.server.data_dir, *entries)
867 if os.path.isdir(file_path):
868 file_path = os.path.join(file_path, 'index.html')
869
870 if not os.path.isfile(file_path):
871 print "File not found " + sub_path + " full path:" + file_path
872 self.send_error(404)
873 return True
874
875 f = open(file_path, "rb")
876 data = f.read()
877 uncompressed_len = len(data)
878 f.close()
879
880 # Compress the data.
881 data = zlib.compress(data)
882 compressed_len = len(data)
883
884 content_length = compressed_len
885 if query == 'U':
886 content_length = uncompressed_len
887 elif query == 'S':
888 content_length = compressed_len / 2
889 elif query == 'M':
890 content_length = (compressed_len + uncompressed_len) / 2
891 elif query == 'L':
892 content_length = compressed_len + uncompressed_len
893
894 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000895 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000896 self.send_header('Content-encoding', 'deflate')
897 self.send_header('Connection', 'close')
898 self.send_header('Content-Length', content_length)
899 self.send_header('ETag', '\'' + file_path + '\'')
900 self.end_headers()
901
902 self.wfile.write(data)
903
904 return True
905
initial.commit94958cf2008-07-26 22:42:52 +0000906 def FileHandler(self):
907 """This handler sends the contents of the requested file. Wow, it's like
908 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000909 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000910 if not self.path.startswith(prefix):
911 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000912 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000913
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000914 def PostOnlyFileHandler(self):
915 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000916 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000917 if not self.path.startswith(prefix):
918 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000919 return self._FileHandlerHelper(prefix)
920
921 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000922 request_body = ''
923 if self.command == 'POST' or self.command == 'PUT':
924 # Consume a request body if present.
925 request_body = self.ReadRequestBody()
926
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000927 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000928 query_dict = cgi.parse_qs(query)
929
930 expected_body = query_dict.get('expected_body', [])
931 if expected_body and request_body not in expected_body:
932 self.send_response(404)
933 self.end_headers()
934 self.wfile.write('')
935 return True
936
937 expected_headers = query_dict.get('expected_headers', [])
938 for expected_header in expected_headers:
939 header_name, expected_value = expected_header.split(':')
940 if self.headers.getheader(header_name) != expected_value:
941 self.send_response(404)
942 self.end_headers()
943 self.wfile.write('')
944 return True
945
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000946 sub_path = url_path[len(prefix):]
947 entries = sub_path.split('/')
948 file_path = os.path.join(self.server.data_dir, *entries)
949 if os.path.isdir(file_path):
950 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000951
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000952 if not os.path.isfile(file_path):
953 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000954 self.send_error(404)
955 return True
956
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000957 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000958 data = f.read()
959 f.close()
960
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000961 data = self._ReplaceFileData(data, query)
962
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000963 old_protocol_version = self.protocol_version
964
initial.commit94958cf2008-07-26 22:42:52 +0000965 # If file.mock-http-headers exists, it contains the headers we
966 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000967 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000968 if os.path.isfile(headers_path):
969 f = open(headers_path, "r")
970
971 # "HTTP/1.1 200 OK"
972 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000973 http_major, http_minor, status_code = re.findall(
974 'HTTP/(\d+).(\d+) (\d+)', response)[0]
975 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000976 self.send_response(int(status_code))
977
978 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000979 header_values = re.findall('(\S+):\s*(.*)', line)
980 if len(header_values) > 0:
981 # "name: value"
982 name, value = header_values[0]
983 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000984 f.close()
985 else:
986 # Could be more generic once we support mime-type sniffing, but for
987 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000988
989 range = self.headers.get('Range')
990 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +0000991 # Note this doesn't handle all valid byte range values (i.e. left
992 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +0000993 range = range[6:].split('-')
994 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000995 if range[1]:
996 end = int(range[1])
997 else:
998 end = len(data)
jam@chromium.org41550782010-11-17 23:47:50 +0000999
1000 self.send_response(206)
1001 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1002 str(len(data))
1003 self.send_header('Content-Range', content_range)
1004 data = data[start: end + 1]
1005 else:
1006 self.send_response(200)
1007
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001008 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001009 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001010 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001011 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001012 self.end_headers()
1013
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001014 if (self.command != 'HEAD'):
1015 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001016
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001017 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001018 return True
1019
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001020 def SetCookieHandler(self):
1021 """This handler just sets a cookie, for testing cookie handling."""
1022
1023 if not self._ShouldHandleRequest("/set-cookie"):
1024 return False
1025
1026 query_char = self.path.find('?')
1027 if query_char != -1:
1028 cookie_values = self.path[query_char + 1:].split('&')
1029 else:
1030 cookie_values = ("",)
1031 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001032 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001033 for cookie_value in cookie_values:
1034 self.send_header('Set-Cookie', '%s' % cookie_value)
1035 self.end_headers()
1036 for cookie_value in cookie_values:
1037 self.wfile.write('%s' % cookie_value)
1038 return True
1039
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001040 def SetHeaderHandler(self):
1041 """This handler sets a response header. Parameters are in the
1042 key%3A%20value&key2%3A%20value2 format."""
1043
1044 if not self._ShouldHandleRequest("/set-header"):
1045 return False
1046
1047 query_char = self.path.find('?')
1048 if query_char != -1:
1049 headers_values = self.path[query_char + 1:].split('&')
1050 else:
1051 headers_values = ("",)
1052 self.send_response(200)
1053 self.send_header('Content-Type', 'text/html')
1054 for header_value in headers_values:
1055 header_value = urllib.unquote(header_value)
1056 (key, value) = header_value.split(': ', 1)
1057 self.send_header(key, value)
1058 self.end_headers()
1059 for header_value in headers_values:
1060 self.wfile.write('%s' % header_value)
1061 return True
1062
initial.commit94958cf2008-07-26 22:42:52 +00001063 def AuthBasicHandler(self):
1064 """This handler tests 'Basic' authentication. It just sends a page with
1065 title 'user/pass' if you succeed."""
1066
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001067 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001068 return False
1069
1070 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001071 expected_password = 'secret'
1072 realm = 'testrealm'
1073 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001074
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001075 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1076 query_params = cgi.parse_qs(query, True)
1077 if 'set-cookie-if-challenged' in query_params:
1078 set_cookie_if_challenged = True
1079 if 'password' in query_params:
1080 expected_password = query_params['password'][0]
1081 if 'realm' in query_params:
1082 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001083
initial.commit94958cf2008-07-26 22:42:52 +00001084 auth = self.headers.getheader('authorization')
1085 try:
1086 if not auth:
1087 raise Exception('no auth')
1088 b64str = re.findall(r'Basic (\S+)', auth)[0]
1089 userpass = base64.b64decode(b64str)
1090 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001091 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001092 raise Exception('wrong password')
1093 except Exception, e:
1094 # Authentication failed.
1095 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001096 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001097 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001098 if set_cookie_if_challenged:
1099 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001100 self.end_headers()
1101 self.wfile.write('<html><head>')
1102 self.wfile.write('<title>Denied: %s</title>' % e)
1103 self.wfile.write('</head><body>')
1104 self.wfile.write('auth=%s<p>' % auth)
1105 self.wfile.write('b64str=%s<p>' % b64str)
1106 self.wfile.write('username: %s<p>' % username)
1107 self.wfile.write('userpass: %s<p>' % userpass)
1108 self.wfile.write('password: %s<p>' % password)
1109 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1110 self.wfile.write('</body></html>')
1111 return True
1112
1113 # Authentication successful. (Return a cachable response to allow for
1114 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001115 old_protocol_version = self.protocol_version
1116 self.protocol_version = "HTTP/1.1"
1117
initial.commit94958cf2008-07-26 22:42:52 +00001118 if_none_match = self.headers.getheader('if-none-match')
1119 if if_none_match == "abc":
1120 self.send_response(304)
1121 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001122 elif url_path.endswith(".gif"):
1123 # Using chrome/test/data/google/logo.gif as the test image
1124 test_image_path = ['google', 'logo.gif']
1125 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1126 if not os.path.isfile(gif_path):
1127 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001128 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001129 return True
1130
1131 f = open(gif_path, "rb")
1132 data = f.read()
1133 f.close()
1134
1135 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001136 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001137 self.send_header('Cache-control', 'max-age=60000')
1138 self.send_header('Etag', 'abc')
1139 self.end_headers()
1140 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001141 else:
1142 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001143 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001144 self.send_header('Cache-control', 'max-age=60000')
1145 self.send_header('Etag', 'abc')
1146 self.end_headers()
1147 self.wfile.write('<html><head>')
1148 self.wfile.write('<title>%s/%s</title>' % (username, password))
1149 self.wfile.write('</head><body>')
1150 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001151 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001152 self.wfile.write('</body></html>')
1153
rvargas@google.com54453b72011-05-19 01:11:11 +00001154 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001155 return True
1156
tonyg@chromium.org75054202010-03-31 22:06:10 +00001157 def GetNonce(self, force_reset=False):
1158 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001159
tonyg@chromium.org75054202010-03-31 22:06:10 +00001160 This is a fake implementation. A real implementation would only use a given
1161 nonce a single time (hence the name n-once). However, for the purposes of
1162 unittesting, we don't care about the security of the nonce.
1163
1164 Args:
1165 force_reset: Iff set, the nonce will be changed. Useful for testing the
1166 "stale" response.
1167 """
1168 if force_reset or not self.server.nonce_time:
1169 self.server.nonce_time = time.time()
1170 return _new_md5('privatekey%s%d' %
1171 (self.path, self.server.nonce_time)).hexdigest()
1172
1173 def AuthDigestHandler(self):
1174 """This handler tests 'Digest' authentication.
1175
1176 It just sends a page with title 'user/pass' if you succeed.
1177
1178 A stale response is sent iff "stale" is present in the request path.
1179 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001180 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001181 return False
1182
tonyg@chromium.org75054202010-03-31 22:06:10 +00001183 stale = 'stale' in self.path
1184 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001185 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001186 password = 'secret'
1187 realm = 'testrealm'
1188
1189 auth = self.headers.getheader('authorization')
1190 pairs = {}
1191 try:
1192 if not auth:
1193 raise Exception('no auth')
1194 if not auth.startswith('Digest'):
1195 raise Exception('not digest')
1196 # Pull out all the name="value" pairs as a dictionary.
1197 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1198
1199 # Make sure it's all valid.
1200 if pairs['nonce'] != nonce:
1201 raise Exception('wrong nonce')
1202 if pairs['opaque'] != opaque:
1203 raise Exception('wrong opaque')
1204
1205 # Check the 'response' value and make sure it matches our magic hash.
1206 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001207 hash_a1 = _new_md5(
1208 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001209 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001210 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001211 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001212 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1213 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001214 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001215
1216 if pairs['response'] != response:
1217 raise Exception('wrong password')
1218 except Exception, e:
1219 # Authentication failed.
1220 self.send_response(401)
1221 hdr = ('Digest '
1222 'realm="%s", '
1223 'domain="/", '
1224 'qop="auth", '
1225 'algorithm=MD5, '
1226 'nonce="%s", '
1227 'opaque="%s"') % (realm, nonce, opaque)
1228 if stale:
1229 hdr += ', stale="TRUE"'
1230 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001231 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001232 self.end_headers()
1233 self.wfile.write('<html><head>')
1234 self.wfile.write('<title>Denied: %s</title>' % e)
1235 self.wfile.write('</head><body>')
1236 self.wfile.write('auth=%s<p>' % auth)
1237 self.wfile.write('pairs=%s<p>' % pairs)
1238 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1239 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1240 self.wfile.write('</body></html>')
1241 return True
1242
1243 # Authentication successful.
1244 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001245 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001246 self.end_headers()
1247 self.wfile.write('<html><head>')
1248 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1249 self.wfile.write('</head><body>')
1250 self.wfile.write('auth=%s<p>' % auth)
1251 self.wfile.write('pairs=%s<p>' % pairs)
1252 self.wfile.write('</body></html>')
1253
1254 return True
1255
1256 def SlowServerHandler(self):
1257 """Wait for the user suggested time before responding. The syntax is
1258 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001259 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001260 return False
1261 query_char = self.path.find('?')
1262 wait_sec = 1.0
1263 if query_char >= 0:
1264 try:
1265 wait_sec = int(self.path[query_char + 1:])
1266 except ValueError:
1267 pass
1268 time.sleep(wait_sec)
1269 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001270 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001271 self.end_headers()
1272 self.wfile.write("waited %d seconds" % wait_sec)
1273 return True
1274
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001275 def ChunkedServerHandler(self):
1276 """Send chunked response. Allows to specify chunks parameters:
1277 - waitBeforeHeaders - ms to wait before sending headers
1278 - waitBetweenChunks - ms to wait between chunks
1279 - chunkSize - size of each chunk in bytes
1280 - chunksNumber - number of chunks
1281 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1282 waits one second, then sends headers and five chunks five bytes each."""
1283 if not self._ShouldHandleRequest("/chunked"):
1284 return False
1285 query_char = self.path.find('?')
1286 chunkedSettings = {'waitBeforeHeaders' : 0,
1287 'waitBetweenChunks' : 0,
1288 'chunkSize' : 5,
1289 'chunksNumber' : 5}
1290 if query_char >= 0:
1291 params = self.path[query_char + 1:].split('&')
1292 for param in params:
1293 keyValue = param.split('=')
1294 if len(keyValue) == 2:
1295 try:
1296 chunkedSettings[keyValue[0]] = int(keyValue[1])
1297 except ValueError:
1298 pass
1299 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1300 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1301 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001302 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001303 self.send_header('Connection', 'close')
1304 self.send_header('Transfer-Encoding', 'chunked')
1305 self.end_headers()
1306 # Chunked encoding: sending all chunks, then final zero-length chunk and
1307 # then final CRLF.
1308 for i in range(0, chunkedSettings['chunksNumber']):
1309 if i > 0:
1310 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1311 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1312 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1313 self.sendChunkHelp('')
1314 return True
1315
initial.commit94958cf2008-07-26 22:42:52 +00001316 def ContentTypeHandler(self):
1317 """Returns a string of html with the given content type. E.g.,
1318 /contenttype?text/css returns an html file with the Content-Type
1319 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001320 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001321 return False
1322 query_char = self.path.find('?')
1323 content_type = self.path[query_char + 1:].strip()
1324 if not content_type:
1325 content_type = 'text/html'
1326 self.send_response(200)
1327 self.send_header('Content-Type', content_type)
1328 self.end_headers()
1329 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1330 return True
1331
creis@google.com2f4f6a42011-03-25 19:44:19 +00001332 def NoContentHandler(self):
1333 """Returns a 204 No Content response."""
1334 if not self._ShouldHandleRequest("/nocontent"):
1335 return False
1336 self.send_response(204)
1337 self.end_headers()
1338 return True
1339
initial.commit94958cf2008-07-26 22:42:52 +00001340 def ServerRedirectHandler(self):
1341 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001342 '/server-redirect?http://foo.bar/asdf' to redirect to
1343 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001344
1345 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001346 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001347 return False
1348
1349 query_char = self.path.find('?')
1350 if query_char < 0 or len(self.path) <= query_char + 1:
1351 self.sendRedirectHelp(test_name)
1352 return True
1353 dest = self.path[query_char + 1:]
1354
1355 self.send_response(301) # moved permanently
1356 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001357 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001358 self.end_headers()
1359 self.wfile.write('<html><head>')
1360 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1361
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001362 return True
initial.commit94958cf2008-07-26 22:42:52 +00001363
1364 def ClientRedirectHandler(self):
1365 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001366 '/client-redirect?http://foo.bar/asdf' to redirect to
1367 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001368
1369 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001370 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001371 return False
1372
1373 query_char = self.path.find('?');
1374 if query_char < 0 or len(self.path) <= query_char + 1:
1375 self.sendRedirectHelp(test_name)
1376 return True
1377 dest = self.path[query_char + 1:]
1378
1379 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001380 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001381 self.end_headers()
1382 self.wfile.write('<html><head>')
1383 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1384 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1385
1386 return True
1387
tony@chromium.org03266982010-03-05 03:18:42 +00001388 def MultipartHandler(self):
1389 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001390 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001391 if not self._ShouldHandleRequest(test_name):
1392 return False
1393
1394 num_frames = 10
1395 bound = '12345'
1396 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001397 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001398 'multipart/x-mixed-replace;boundary=' + bound)
1399 self.end_headers()
1400
1401 for i in xrange(num_frames):
1402 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001403 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001404 self.wfile.write('<title>page ' + str(i) + '</title>')
1405 self.wfile.write('page ' + str(i))
1406
1407 self.wfile.write('--' + bound + '--')
1408 return True
1409
tony@chromium.org4cb88302011-09-27 22:13:49 +00001410 def MultipartSlowHandler(self):
1411 """Send a multipart response (3 text/html pages) with a slight delay
1412 between each page. This is similar to how some pages show status using
1413 multipart."""
1414 test_name = '/multipart-slow'
1415 if not self._ShouldHandleRequest(test_name):
1416 return False
1417
1418 num_frames = 3
1419 bound = '12345'
1420 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001421 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001422 'multipart/x-mixed-replace;boundary=' + bound)
1423 self.end_headers()
1424
1425 for i in xrange(num_frames):
1426 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001427 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001428 time.sleep(0.25)
1429 if i == 2:
1430 self.wfile.write('<title>PASS</title>')
1431 else:
1432 self.wfile.write('<title>page ' + str(i) + '</title>')
1433 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1434
1435 self.wfile.write('--' + bound + '--')
1436 return True
1437
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001438 def GetSSLSessionCacheHandler(self):
1439 """Send a reply containing a log of the session cache operations."""
1440
1441 if not self._ShouldHandleRequest('/ssl-session-cache'):
1442 return False
1443
1444 self.send_response(200)
1445 self.send_header('Content-Type', 'text/plain')
1446 self.end_headers()
1447 try:
1448 for (action, sessionID) in self.server.session_cache.log:
1449 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1450 except AttributeError, e:
1451 self.wfile.write('Pass --https-record-resume in order to use' +
1452 ' this request')
1453 return True
1454
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001455 def CloseSocketHandler(self):
1456 """Closes the socket without sending anything."""
1457
1458 if not self._ShouldHandleRequest('/close-socket'):
1459 return False
1460
1461 self.wfile.close()
1462 return True
1463
initial.commit94958cf2008-07-26 22:42:52 +00001464 def DefaultResponseHandler(self):
1465 """This is the catch-all response handler for requests that aren't handled
1466 by one of the special handlers above.
1467 Note that we specify the content-length as without it the https connection
1468 is not closed properly (and the browser keeps expecting data)."""
1469
1470 contents = "Default response given for path: " + self.path
1471 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001472 self.send_header('Content-Type', 'text/html')
1473 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001474 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001475 if (self.command != 'HEAD'):
1476 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001477 return True
1478
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001479 def RedirectConnectHandler(self):
1480 """Sends a redirect to the CONNECT request for www.redirect.com. This
1481 response is not specified by the RFC, so the browser should not follow
1482 the redirect."""
1483
1484 if (self.path.find("www.redirect.com") < 0):
1485 return False
1486
1487 dest = "http://www.destination.com/foo.js"
1488
1489 self.send_response(302) # moved temporarily
1490 self.send_header('Location', dest)
1491 self.send_header('Connection', 'close')
1492 self.end_headers()
1493 return True
1494
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001495 def ServerAuthConnectHandler(self):
1496 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1497 response doesn't make sense because the proxy server cannot request
1498 server authentication."""
1499
1500 if (self.path.find("www.server-auth.com") < 0):
1501 return False
1502
1503 challenge = 'Basic realm="WallyWorld"'
1504
1505 self.send_response(401) # unauthorized
1506 self.send_header('WWW-Authenticate', challenge)
1507 self.send_header('Connection', 'close')
1508 self.end_headers()
1509 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001510
1511 def DefaultConnectResponseHandler(self):
1512 """This is the catch-all response handler for CONNECT requests that aren't
1513 handled by one of the special handlers above. Real Web servers respond
1514 with 400 to CONNECT requests."""
1515
1516 contents = "Your client has issued a malformed or illegal request."
1517 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001518 self.send_header('Content-Type', 'text/html')
1519 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001520 self.end_headers()
1521 self.wfile.write(contents)
1522 return True
1523
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001524 def DeviceManagementHandler(self):
1525 """Delegates to the device management service used for cloud policy."""
1526 if not self._ShouldHandleRequest("/device_management"):
1527 return False
1528
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001529 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001530
1531 if not self.server._device_management_handler:
1532 import device_management
1533 policy_path = os.path.join(self.server.data_dir, 'device_management')
1534 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001535 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001536 self.server.policy_keys,
1537 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001538
1539 http_response, raw_reply = (
1540 self.server._device_management_handler.HandleRequest(self.path,
1541 self.headers,
1542 raw_request))
1543 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001544 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001545 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001546 self.end_headers()
1547 self.wfile.write(raw_reply)
1548 return True
1549
initial.commit94958cf2008-07-26 22:42:52 +00001550 # called by the redirect handling function when there is no parameter
1551 def sendRedirectHelp(self, redirect_name):
1552 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001553 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001554 self.end_headers()
1555 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1556 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1557 self.wfile.write('</body></html>')
1558
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001559 # called by chunked handling function
1560 def sendChunkHelp(self, chunk):
1561 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1562 self.wfile.write('%X\r\n' % len(chunk))
1563 self.wfile.write(chunk)
1564 self.wfile.write('\r\n')
1565
akalin@chromium.org154bb132010-11-12 02:20:27 +00001566
1567class SyncPageHandler(BasePageHandler):
1568 """Handler for the main HTTP sync server."""
1569
1570 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001571 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001572 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001573 self.ChromiumSyncDisableNotificationsOpHandler,
1574 self.ChromiumSyncEnableNotificationsOpHandler,
1575 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001576 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001577 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001578 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001579 self.ChromiumSyncErrorOpHandler,
1580 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001581
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001582 post_handlers = [self.ChromiumSyncCommandHandler,
1583 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001584 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001585 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001586 post_handlers, [])
1587
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001588
akalin@chromium.org154bb132010-11-12 02:20:27 +00001589 def ChromiumSyncTimeHandler(self):
1590 """Handle Chromium sync .../time requests.
1591
1592 The syncer sometimes checks server reachability by examining /time.
1593 """
1594 test_name = "/chromiumsync/time"
1595 if not self._ShouldHandleRequest(test_name):
1596 return False
1597
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001598 # Chrome hates it if we send a response before reading the request.
1599 if self.headers.getheader('content-length'):
1600 length = int(self.headers.getheader('content-length'))
1601 raw_request = self.rfile.read(length)
1602
akalin@chromium.org154bb132010-11-12 02:20:27 +00001603 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001604 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001605 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001606 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001607 return True
1608
1609 def ChromiumSyncCommandHandler(self):
1610 """Handle a chromiumsync command arriving via http.
1611
1612 This covers all sync protocol commands: authentication, getupdates, and
1613 commit.
1614 """
1615 test_name = "/chromiumsync/command"
1616 if not self._ShouldHandleRequest(test_name):
1617 return False
1618
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001619 length = int(self.headers.getheader('content-length'))
1620 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001621 http_response = 200
1622 raw_reply = None
1623 if not self.server.GetAuthenticated():
1624 http_response = 401
1625 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1626 else:
1627 http_response, raw_reply = self.server.HandleCommand(
1628 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001629
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001630 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001631 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001632 if http_response == 401:
1633 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001634 self.end_headers()
1635 self.wfile.write(raw_reply)
1636 return True
1637
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001638 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001639 test_name = "/chromiumsync/migrate"
1640 if not self._ShouldHandleRequest(test_name):
1641 return False
1642
1643 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1644 self.path)
1645 self.send_response(http_response)
1646 self.send_header('Content-Type', 'text/html')
1647 self.send_header('Content-Length', len(raw_reply))
1648 self.end_headers()
1649 self.wfile.write(raw_reply)
1650 return True
1651
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001652 def ChromiumSyncCredHandler(self):
1653 test_name = "/chromiumsync/cred"
1654 if not self._ShouldHandleRequest(test_name):
1655 return False
1656 try:
1657 query = urlparse.urlparse(self.path)[4]
1658 cred_valid = urlparse.parse_qs(query)['valid']
1659 if cred_valid[0] == 'True':
1660 self.server.SetAuthenticated(True)
1661 else:
1662 self.server.SetAuthenticated(False)
1663 except:
1664 self.server.SetAuthenticated(False)
1665
1666 http_response = 200
1667 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1668 self.send_response(http_response)
1669 self.send_header('Content-Type', 'text/html')
1670 self.send_header('Content-Length', len(raw_reply))
1671 self.end_headers()
1672 self.wfile.write(raw_reply)
1673 return True
1674
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001675 def ChromiumSyncDisableNotificationsOpHandler(self):
1676 test_name = "/chromiumsync/disablenotifications"
1677 if not self._ShouldHandleRequest(test_name):
1678 return False
1679 self.server.GetXmppServer().DisableNotifications()
1680 result = 200
1681 raw_reply = ('<html><title>Notifications disabled</title>'
1682 '<H1>Notifications disabled</H1></html>')
1683 self.send_response(result)
1684 self.send_header('Content-Type', 'text/html')
1685 self.send_header('Content-Length', len(raw_reply))
1686 self.end_headers()
1687 self.wfile.write(raw_reply)
1688 return True;
1689
1690 def ChromiumSyncEnableNotificationsOpHandler(self):
1691 test_name = "/chromiumsync/enablenotifications"
1692 if not self._ShouldHandleRequest(test_name):
1693 return False
1694 self.server.GetXmppServer().EnableNotifications()
1695 result = 200
1696 raw_reply = ('<html><title>Notifications enabled</title>'
1697 '<H1>Notifications enabled</H1></html>')
1698 self.send_response(result)
1699 self.send_header('Content-Type', 'text/html')
1700 self.send_header('Content-Length', len(raw_reply))
1701 self.end_headers()
1702 self.wfile.write(raw_reply)
1703 return True;
1704
1705 def ChromiumSyncSendNotificationOpHandler(self):
1706 test_name = "/chromiumsync/sendnotification"
1707 if not self._ShouldHandleRequest(test_name):
1708 return False
1709 query = urlparse.urlparse(self.path)[4]
1710 query_params = urlparse.parse_qs(query)
1711 channel = ''
1712 data = ''
1713 if 'channel' in query_params:
1714 channel = query_params['channel'][0]
1715 if 'data' in query_params:
1716 data = query_params['data'][0]
1717 self.server.GetXmppServer().SendNotification(channel, data)
1718 result = 200
1719 raw_reply = ('<html><title>Notification sent</title>'
1720 '<H1>Notification sent with channel "%s" '
1721 'and data "%s"</H1></html>'
1722 % (channel, data))
1723 self.send_response(result)
1724 self.send_header('Content-Type', 'text/html')
1725 self.send_header('Content-Length', len(raw_reply))
1726 self.end_headers()
1727 self.wfile.write(raw_reply)
1728 return True;
1729
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001730 def ChromiumSyncBirthdayErrorOpHandler(self):
1731 test_name = "/chromiumsync/birthdayerror"
1732 if not self._ShouldHandleRequest(test_name):
1733 return False
1734 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1735 self.send_response(result)
1736 self.send_header('Content-Type', 'text/html')
1737 self.send_header('Content-Length', len(raw_reply))
1738 self.end_headers()
1739 self.wfile.write(raw_reply)
1740 return True;
1741
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001742 def ChromiumSyncTransientErrorOpHandler(self):
1743 test_name = "/chromiumsync/transienterror"
1744 if not self._ShouldHandleRequest(test_name):
1745 return False
1746 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1747 self.send_response(result)
1748 self.send_header('Content-Type', 'text/html')
1749 self.send_header('Content-Length', len(raw_reply))
1750 self.end_headers()
1751 self.wfile.write(raw_reply)
1752 return True;
1753
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001754 def ChromiumSyncErrorOpHandler(self):
1755 test_name = "/chromiumsync/error"
1756 if not self._ShouldHandleRequest(test_name):
1757 return False
1758 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1759 self.path)
1760 self.send_response(result)
1761 self.send_header('Content-Type', 'text/html')
1762 self.send_header('Content-Length', len(raw_reply))
1763 self.end_headers()
1764 self.wfile.write(raw_reply)
1765 return True;
1766
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001767 def ChromiumSyncSyncTabsOpHandler(self):
1768 test_name = "/chromiumsync/synctabs"
1769 if not self._ShouldHandleRequest(test_name):
1770 return False
1771 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1772 self.send_response(result)
1773 self.send_header('Content-Type', 'text/html')
1774 self.send_header('Content-Length', len(raw_reply))
1775 self.end_headers()
1776 self.wfile.write(raw_reply)
1777 return True;
1778
akalin@chromium.org154bb132010-11-12 02:20:27 +00001779
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001780def MakeDataDir():
1781 if options.data_dir:
1782 if not os.path.isdir(options.data_dir):
1783 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1784 return None
1785 my_data_dir = options.data_dir
1786 else:
1787 # Create the default path to our data dir, relative to the exe dir.
1788 my_data_dir = os.path.dirname(sys.argv[0])
1789 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001790 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001791
1792 #TODO(ibrar): Must use Find* funtion defined in google\tools
1793 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1794
1795 return my_data_dir
1796
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001797
1798class TCPEchoHandler(SocketServer.BaseRequestHandler):
1799 """The RequestHandler class for TCP echo server.
1800
1801 It is instantiated once per connection to the server, and overrides the
1802 handle() method to implement communication to the client.
1803 """
1804
1805 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001806 """Handles the request from the client and constructs a response."""
1807
1808 data = self.request.recv(65536).strip()
1809 # Verify the "echo request" message received from the client. Send back
1810 # "echo response" message if "echo request" message is valid.
1811 try:
1812 return_data = echo_message.GetEchoResponseData(data)
1813 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001814 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001815 except ValueError:
1816 return
1817
1818 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001819
1820
1821class UDPEchoHandler(SocketServer.BaseRequestHandler):
1822 """The RequestHandler class for UDP echo server.
1823
1824 It is instantiated once per connection to the server, and overrides the
1825 handle() method to implement communication to the client.
1826 """
1827
1828 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001829 """Handles the request from the client and constructs a response."""
1830
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001831 data = self.request[0].strip()
1832 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001833 # Verify the "echo request" message received from the client. Send back
1834 # "echo response" message if "echo request" message is valid.
1835 try:
1836 return_data = echo_message.GetEchoResponseData(data)
1837 if not return_data:
1838 return
1839 except ValueError:
1840 return
1841 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001842
1843
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001844class FileMultiplexer:
1845 def __init__(self, fd1, fd2) :
1846 self.__fd1 = fd1
1847 self.__fd2 = fd2
1848
1849 def __del__(self) :
1850 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1851 self.__fd1.close()
1852 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1853 self.__fd2.close()
1854
1855 def write(self, text) :
1856 self.__fd1.write(text)
1857 self.__fd2.write(text)
1858
1859 def flush(self) :
1860 self.__fd1.flush()
1861 self.__fd2.flush()
1862
initial.commit94958cf2008-07-26 22:42:52 +00001863def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001864 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001865 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001866 if options.log_to_console:
1867 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1868 else:
1869 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001870
1871 port = options.port
1872
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001873 server_data = {}
1874
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001875 if options.server_type == SERVER_HTTP:
1876 if options.cert:
1877 # let's make sure the cert file exists.
1878 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001879 print 'specified server cert file not found: ' + options.cert + \
1880 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001881 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001882 for ca_cert in options.ssl_client_ca:
1883 if not os.path.isfile(ca_cert):
1884 print 'specified trusted client CA file not found: ' + ca_cert + \
1885 ' exiting...'
1886 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001887 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001888 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001889 options.ssl_bulk_cipher, options.record_resume)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001890 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001891 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001892 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001893 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001894
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001895 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001896 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001897 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001898 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001899 server.policy_keys = options.policy_keys
1900 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001901 elif options.server_type == SERVER_SYNC:
1902 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1903 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001904 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1905 server_data['port'] = server.server_port
1906 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001907 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001908 # Used for generating the key (randomly) that encodes the "echo request"
1909 # message.
1910 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001911 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1912 print 'Echo TCP server started on port %d...' % server.server_port
1913 server_data['port'] = server.server_port
1914 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001915 # Used for generating the key (randomly) that encodes the "echo request"
1916 # message.
1917 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001918 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1919 print 'Echo UDP server started on port %d...' % server.server_port
1920 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001921 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001922 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001923 my_data_dir = MakeDataDir()
1924
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001925 # Instantiate a dummy authorizer for managing 'virtual' users
1926 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1927
1928 # Define a new user having full r/w permissions and a read-only
1929 # anonymous user
1930 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1931
1932 authorizer.add_anonymous(my_data_dir)
1933
1934 # Instantiate FTP handler class
1935 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1936 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001937
1938 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001939 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1940 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001941
1942 # Instantiate FTP server class and listen to 127.0.0.1:port
1943 address = ('127.0.0.1', port)
1944 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001945 server_data['port'] = server.socket.getsockname()[1]
1946 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001947
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001948 # Notify the parent that we've started. (BaseServer subclasses
1949 # bind their sockets on construction.)
1950 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001951 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001952 server_data_len = len(server_data_json)
1953 print 'sending server_data: %s (%d bytes)' % (
1954 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001955 if sys.platform == 'win32':
1956 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1957 else:
1958 fd = options.startup_pipe
1959 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001960 # First write the data length as an unsigned 4-byte value. This
1961 # is _not_ using network byte ordering since the other end of the
1962 # pipe is on the same machine.
1963 startup_pipe.write(struct.pack('=L', server_data_len))
1964 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001965 startup_pipe.close()
1966
initial.commit94958cf2008-07-26 22:42:52 +00001967 try:
1968 server.serve_forever()
1969 except KeyboardInterrupt:
1970 print 'shutting down server'
1971 server.stop = True
1972
1973if __name__ == '__main__':
1974 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001975 option_parser.add_option("-f", '--ftp', action='store_const',
1976 const=SERVER_FTP, default=SERVER_HTTP,
1977 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001978 help='start up an FTP server.')
1979 option_parser.add_option('', '--sync', action='store_const',
1980 const=SERVER_SYNC, default=SERVER_HTTP,
1981 dest='server_type',
1982 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001983 option_parser.add_option('', '--tcp-echo', action='store_const',
1984 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1985 dest='server_type',
1986 help='start up a tcp echo server.')
1987 option_parser.add_option('', '--udp-echo', action='store_const',
1988 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1989 dest='server_type',
1990 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001991 option_parser.add_option('', '--log-to-console', action='store_const',
1992 const=True, default=False,
1993 dest='log_to_console',
1994 help='Enables or disables sys.stdout logging to '
1995 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001996 option_parser.add_option('', '--port', default='0', type='int',
1997 help='Port used by the server. If unspecified, the '
1998 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001999 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002000 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002001 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00002002 help='Specify that https should be used, specify '
2003 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002004 'the server should use.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002005 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2006 const=True, default=False, action='store_const',
2007 help='Record resumption cache events rather than'
2008 ' resuming as normal. Allows the use of the'
2009 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002010 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2011 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002012 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2013 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002014 'should include the CA named in the subject of '
2015 'the DER-encoded certificate contained in the '
2016 'specified file. This option may appear multiple '
2017 'times, indicating multiple CA names should be '
2018 'sent in the request.')
2019 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2020 help='Specify the bulk encryption algorithm(s)'
2021 'that will be accepted by the SSL server. Valid '
2022 'values are "aes256", "aes128", "3des", "rc4". If '
2023 'omitted, all algorithms will be used. This '
2024 'option may appear multiple times, indicating '
2025 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002026 option_parser.add_option('', '--file-root-url', default='/files/',
2027 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002028 option_parser.add_option('', '--startup-pipe', type='int',
2029 dest='startup_pipe',
2030 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002031 option_parser.add_option('', '--policy-key', action='append',
2032 dest='policy_keys',
2033 help='Specify a path to a PEM-encoded private key '
2034 'to use for policy signing. May be specified '
2035 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002036 'the server. If ther server has multiple keys, it '
2037 'will rotate through them in at each request a '
2038 'round-robin fashion. The server will generate a '
2039 'random key if none is specified on the command '
2040 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002041 option_parser.add_option('', '--policy-user', default='user@example.com',
2042 dest='policy_user',
2043 help='Specify the user name the server should '
2044 'report back to the client as the user owning the '
2045 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00002046 options, args = option_parser.parse_args()
2047
2048 sys.exit(main(options, args))