blob: 48defa3180f41dd7d0e873e70f6ed9cc495d8252 [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
erikwright@chromium.org847ef282012-02-22 16:41:10 +000085
86class ClientRestrictingServerMixIn:
87 """Implements verify_request to limit connections to our configured IP
88 address."""
89
90 def verify_request(self, request, client_address):
91 return client_address[0] == self.server_address[0]
92
93
initial.commit94958cf2008-07-26 22:42:52 +000094class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +000095 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +000096 to be exited cleanly (by setting its "stop" member to True)."""
97
98 def serve_forever(self):
99 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000100 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000101 while not self.stop:
102 self.handle_request()
103 self.socket.close()
104
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000105
106class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
107 """This is a specialization of StoppableHTTPerver that adds client
108 verification."""
109
110 pass
111
112
113class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
114 ClientRestrictingServerMixIn,
115 StoppableHTTPServer):
116 """This is a specialization of StoppableHTTPerver that add https support and
117 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000118
davidben@chromium.org31282a12010-08-07 01:10:02 +0000119 def __init__(self, server_address, request_hander_class, cert_path,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000120 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
121 record_resume_info):
initial.commit94958cf2008-07-26 22:42:52 +0000122 s = open(cert_path).read()
rsleevi@chromium.orge35b5fc2012-03-02 14:58:02 +0000123 self.cert_chain = tlslite.api.X509CertChain().parseChain(s)
initial.commit94958cf2008-07-26 22:42:52 +0000124 s = open(cert_path).read()
125 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000126 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000127 self.ssl_client_cas = []
128 for ca_file in ssl_client_cas:
129 s = open(ca_file).read()
130 x509 = tlslite.api.X509()
131 x509.parse(s)
132 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000133 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
134 if ssl_bulk_ciphers is not None:
135 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000136
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000137 if record_resume_info:
138 # If record_resume_info is true then we'll replace the session cache with
139 # an object that records the lookups and inserts that it sees.
140 self.session_cache = RecordingSSLSessionCache()
141 else:
142 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000143 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
144
145 def handshake(self, tlsConnection):
146 """Creates the SSL connection."""
147 try:
148 tlsConnection.handshakeServer(certChain=self.cert_chain,
149 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000150 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000151 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000152 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000153 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000154 tlsConnection.ignoreAbruptClose = True
155 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000156 except tlslite.api.TLSAbruptCloseError:
157 # Ignore abrupt close.
158 return True
initial.commit94958cf2008-07-26 22:42:52 +0000159 except tlslite.api.TLSError, error:
160 print "Handshake failure:", str(error)
161 return False
162
akalin@chromium.org154bb132010-11-12 02:20:27 +0000163
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000164class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000165 """An HTTP server that handles sync commands."""
166
167 def __init__(self, server_address, request_handler_class):
168 # We import here to avoid pulling in chromiumsync's dependencies
169 # unless strictly necessary.
170 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000171 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000172 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000173 self._sync_handler = chromiumsync.TestServer()
174 self._xmpp_socket_map = {}
175 self._xmpp_server = xmppserver.XmppServer(
176 self._xmpp_socket_map, ('localhost', 0))
177 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000178 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000179
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000180 def GetXmppServer(self):
181 return self._xmpp_server
182
akalin@chromium.org154bb132010-11-12 02:20:27 +0000183 def HandleCommand(self, query, raw_request):
184 return self._sync_handler.HandleCommand(query, raw_request)
185
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000186 def HandleRequestNoBlock(self):
187 """Handles a single request.
188
189 Copied from SocketServer._handle_request_noblock().
190 """
191 try:
192 request, client_address = self.get_request()
193 except socket.error:
194 return
195 if self.verify_request(request, client_address):
196 try:
197 self.process_request(request, client_address)
198 except:
199 self.handle_error(request, client_address)
200 self.close_request(request)
201
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000202 def SetAuthenticated(self, auth_valid):
203 self.authenticated = auth_valid
204
205 def GetAuthenticated(self):
206 return self.authenticated
207
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000208 def serve_forever(self):
209 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
210 """
211
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000212 def HandleXmppSocket(fd, socket_map, handler):
213 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000214
215 Adapted from asyncore.read() et al.
216 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000217 xmpp_connection = socket_map.get(fd)
218 # This could happen if a previous handler call caused fd to get
219 # removed from socket_map.
220 if xmpp_connection is None:
221 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000222 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000223 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000224 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
225 raise
226 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000227 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000228
229 while True:
230 read_fds = [ self.fileno() ]
231 write_fds = []
232 exceptional_fds = []
233
234 for fd, xmpp_connection in self._xmpp_socket_map.items():
235 is_r = xmpp_connection.readable()
236 is_w = xmpp_connection.writable()
237 if is_r:
238 read_fds.append(fd)
239 if is_w:
240 write_fds.append(fd)
241 if is_r or is_w:
242 exceptional_fds.append(fd)
243
244 try:
245 read_fds, write_fds, exceptional_fds = (
246 select.select(read_fds, write_fds, exceptional_fds))
247 except select.error, err:
248 if err.args[0] != errno.EINTR:
249 raise
250 else:
251 continue
252
253 for fd in read_fds:
254 if fd == self.fileno():
255 self.HandleRequestNoBlock()
256 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000257 HandleXmppSocket(fd, self._xmpp_socket_map,
258 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000259
260 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000261 HandleXmppSocket(fd, self._xmpp_socket_map,
262 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000263
264 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000265 HandleXmppSocket(fd, self._xmpp_socket_map,
266 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000267
akalin@chromium.org154bb132010-11-12 02:20:27 +0000268
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000269class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
270 """This is a specialization of FTPServer that adds client verification."""
271
272 pass
273
274
275class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000276 """A TCP echo server that echoes back what it has received."""
277
278 def server_bind(self):
279 """Override server_bind to store the server name."""
280 SocketServer.TCPServer.server_bind(self)
281 host, port = self.socket.getsockname()[:2]
282 self.server_name = socket.getfqdn(host)
283 self.server_port = port
284
285 def serve_forever(self):
286 self.stop = False
287 self.nonce_time = None
288 while not self.stop:
289 self.handle_request()
290 self.socket.close()
291
292
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000293class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000294 """A UDP echo server that echoes back what it has received."""
295
296 def server_bind(self):
297 """Override server_bind to store the server name."""
298 SocketServer.UDPServer.server_bind(self)
299 host, port = self.socket.getsockname()[:2]
300 self.server_name = socket.getfqdn(host)
301 self.server_port = port
302
303 def serve_forever(self):
304 self.stop = False
305 self.nonce_time = None
306 while not self.stop:
307 self.handle_request()
308 self.socket.close()
309
310
akalin@chromium.org154bb132010-11-12 02:20:27 +0000311class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
312
313 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000314 connect_handlers, get_handlers, head_handlers, post_handlers,
315 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000316 self._connect_handlers = connect_handlers
317 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000318 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000319 self._post_handlers = post_handlers
320 self._put_handlers = put_handlers
321 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
322 self, request, client_address, socket_server)
323
324 def log_request(self, *args, **kwargs):
325 # Disable request logging to declutter test log output.
326 pass
327
328 def _ShouldHandleRequest(self, handler_name):
329 """Determines if the path can be handled by the handler.
330
331 We consider a handler valid if the path begins with the
332 handler name. It can optionally be followed by "?*", "/*".
333 """
334
335 pattern = re.compile('%s($|\?|/).*' % handler_name)
336 return pattern.match(self.path)
337
338 def do_CONNECT(self):
339 for handler in self._connect_handlers:
340 if handler():
341 return
342
343 def do_GET(self):
344 for handler in self._get_handlers:
345 if handler():
346 return
347
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000348 def do_HEAD(self):
349 for handler in self._head_handlers:
350 if handler():
351 return
352
akalin@chromium.org154bb132010-11-12 02:20:27 +0000353 def do_POST(self):
354 for handler in self._post_handlers:
355 if handler():
356 return
357
358 def do_PUT(self):
359 for handler in self._put_handlers:
360 if handler():
361 return
362
363
364class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000365
366 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000367 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000368 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000369 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000370 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000371 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000372 self.NoCacheMaxAgeTimeHandler,
373 self.NoCacheTimeHandler,
374 self.CacheTimeHandler,
375 self.CacheExpiresHandler,
376 self.CacheProxyRevalidateHandler,
377 self.CachePrivateHandler,
378 self.CachePublicHandler,
379 self.CacheSMaxAgeHandler,
380 self.CacheMustRevalidateHandler,
381 self.CacheMustRevalidateMaxAgeHandler,
382 self.CacheNoStoreHandler,
383 self.CacheNoStoreMaxAgeHandler,
384 self.CacheNoTransformHandler,
385 self.DownloadHandler,
386 self.DownloadFinishHandler,
387 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000388 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000389 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000390 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000391 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000392 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000393 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000394 self.AuthBasicHandler,
395 self.AuthDigestHandler,
396 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000397 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000398 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000399 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000400 self.ServerRedirectHandler,
401 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000402 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000403 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000404 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000405 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000406 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000407 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000408 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000409 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000410 self.DeviceManagementHandler,
411 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000412 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000413 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000414 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000415 head_handlers = [
416 self.FileHandler,
417 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000418
maruel@google.come250a9b2009-03-10 17:39:46 +0000419 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000420 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000421 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000422 'gif': 'image/gif',
423 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000424 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000425 'pdf' : 'application/pdf',
426 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000427 }
initial.commit94958cf2008-07-26 22:42:52 +0000428 self._default_mime_type = 'text/html'
429
akalin@chromium.org154bb132010-11-12 02:20:27 +0000430 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000431 connect_handlers, get_handlers, head_handlers,
432 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000433
initial.commit94958cf2008-07-26 22:42:52 +0000434 def GetMIMETypeFromName(self, file_name):
435 """Returns the mime type for the specified file_name. So far it only looks
436 at the file extension."""
437
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000438 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000439 if len(extension) == 0:
440 # no extension.
441 return self._default_mime_type
442
ericroman@google.comc17ca532009-05-07 03:51:05 +0000443 # extension starts with a dot, so we need to remove it
444 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000445
initial.commit94958cf2008-07-26 22:42:52 +0000446 def NoCacheMaxAgeTimeHandler(self):
447 """This request handler yields a page with the title set to the current
448 system time, and no caching requested."""
449
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000450 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000451 return False
452
453 self.send_response(200)
454 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000455 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000456 self.end_headers()
457
maruel@google.come250a9b2009-03-10 17:39:46 +0000458 self.wfile.write('<html><head><title>%s</title></head></html>' %
459 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000460
461 return True
462
463 def NoCacheTimeHandler(self):
464 """This request handler yields a page with the title set to the current
465 system time, and no caching requested."""
466
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000467 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000468 return False
469
470 self.send_response(200)
471 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000472 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000473 self.end_headers()
474
maruel@google.come250a9b2009-03-10 17:39:46 +0000475 self.wfile.write('<html><head><title>%s</title></head></html>' %
476 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000477
478 return True
479
480 def CacheTimeHandler(self):
481 """This request handler yields a page with the title set to the current
482 system time, and allows caching for one minute."""
483
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000484 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000485 return False
486
487 self.send_response(200)
488 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000489 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000490 self.end_headers()
491
maruel@google.come250a9b2009-03-10 17:39:46 +0000492 self.wfile.write('<html><head><title>%s</title></head></html>' %
493 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000494
495 return True
496
497 def CacheExpiresHandler(self):
498 """This request handler yields a page with the title set to the current
499 system time, and set the page to expire on 1 Jan 2099."""
500
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000501 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000502 return False
503
504 self.send_response(200)
505 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000506 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000507 self.end_headers()
508
maruel@google.come250a9b2009-03-10 17:39:46 +0000509 self.wfile.write('<html><head><title>%s</title></head></html>' %
510 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000511
512 return True
513
514 def CacheProxyRevalidateHandler(self):
515 """This request handler yields a page with the title set to the current
516 system time, and allows caching for 60 seconds"""
517
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000518 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000519 return False
520
521 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000522 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000523 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
524 self.end_headers()
525
maruel@google.come250a9b2009-03-10 17:39:46 +0000526 self.wfile.write('<html><head><title>%s</title></head></html>' %
527 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000528
529 return True
530
531 def CachePrivateHandler(self):
532 """This request handler yields a page with the title set to the current
533 system time, and allows caching for 5 seconds."""
534
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000535 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000536 return False
537
538 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000539 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000540 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000541 self.end_headers()
542
maruel@google.come250a9b2009-03-10 17:39:46 +0000543 self.wfile.write('<html><head><title>%s</title></head></html>' %
544 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000545
546 return True
547
548 def CachePublicHandler(self):
549 """This request handler yields a page with the title set to the current
550 system time, and allows caching for 5 seconds."""
551
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000552 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000553 return False
554
555 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000556 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000557 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000558 self.end_headers()
559
maruel@google.come250a9b2009-03-10 17:39:46 +0000560 self.wfile.write('<html><head><title>%s</title></head></html>' %
561 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000562
563 return True
564
565 def CacheSMaxAgeHandler(self):
566 """This request handler yields a page with the title set to the current
567 system time, and does not allow for caching."""
568
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000569 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000570 return False
571
572 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000573 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000574 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
575 self.end_headers()
576
maruel@google.come250a9b2009-03-10 17:39:46 +0000577 self.wfile.write('<html><head><title>%s</title></head></html>' %
578 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000579
580 return True
581
582 def CacheMustRevalidateHandler(self):
583 """This request handler yields a page with the title set to the current
584 system time, and does not allow caching."""
585
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000586 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000587 return False
588
589 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000590 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000591 self.send_header('Cache-Control', 'must-revalidate')
592 self.end_headers()
593
maruel@google.come250a9b2009-03-10 17:39:46 +0000594 self.wfile.write('<html><head><title>%s</title></head></html>' %
595 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000596
597 return True
598
599 def CacheMustRevalidateMaxAgeHandler(self):
600 """This request handler yields a page with the title set to the current
601 system time, and does not allow caching event though max-age of 60
602 seconds is specified."""
603
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000604 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000605 return False
606
607 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000608 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000609 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
610 self.end_headers()
611
maruel@google.come250a9b2009-03-10 17:39:46 +0000612 self.wfile.write('<html><head><title>%s</title></head></html>' %
613 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000614
615 return True
616
initial.commit94958cf2008-07-26 22:42:52 +0000617 def CacheNoStoreHandler(self):
618 """This request handler yields a page with the title set to the current
619 system time, and does not allow the page to be stored."""
620
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000621 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000622 return False
623
624 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000625 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000626 self.send_header('Cache-Control', 'no-store')
627 self.end_headers()
628
maruel@google.come250a9b2009-03-10 17:39:46 +0000629 self.wfile.write('<html><head><title>%s</title></head></html>' %
630 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000631
632 return True
633
634 def CacheNoStoreMaxAgeHandler(self):
635 """This request handler yields a page with the title set to the current
636 system time, and does not allow the page to be stored even though max-age
637 of 60 seconds is specified."""
638
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000639 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000640 return False
641
642 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000643 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000644 self.send_header('Cache-Control', 'max-age=60, no-store')
645 self.end_headers()
646
maruel@google.come250a9b2009-03-10 17:39:46 +0000647 self.wfile.write('<html><head><title>%s</title></head></html>' %
648 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000649
650 return True
651
652
653 def CacheNoTransformHandler(self):
654 """This request handler yields a page with the title set to the current
655 system time, and does not allow the content to transformed during
656 user-agent caching"""
657
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000658 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000659 return False
660
661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000662 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000663 self.send_header('Cache-Control', 'no-transform')
664 self.end_headers()
665
maruel@google.come250a9b2009-03-10 17:39:46 +0000666 self.wfile.write('<html><head><title>%s</title></head></html>' %
667 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000668
669 return True
670
671 def EchoHeader(self):
672 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000673 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000674
ananta@chromium.org56812d02011-04-07 17:52:05 +0000675 """This function echoes back the value of a specific request header"""
676 """while allowing caching for 16 hours."""
677 def EchoHeaderCache(self):
678 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000679
680 def EchoHeaderHelper(self, echo_header):
681 """This function echoes back the value of the request header passed in."""
682 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000683 return False
684
685 query_char = self.path.find('?')
686 if query_char != -1:
687 header_name = self.path[query_char+1:]
688
689 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000690 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000691 if echo_header == '/echoheadercache':
692 self.send_header('Cache-control', 'max-age=60000')
693 else:
694 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000695 # insert a vary header to properly indicate that the cachability of this
696 # request is subject to value of the request header being echoed.
697 if len(header_name) > 0:
698 self.send_header('Vary', header_name)
699 self.end_headers()
700
701 if len(header_name) > 0:
702 self.wfile.write(self.headers.getheader(header_name))
703
704 return True
705
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000706 def ReadRequestBody(self):
707 """This function reads the body of the current HTTP request, handling
708 both plain and chunked transfer encoded requests."""
709
710 if self.headers.getheader('transfer-encoding') != 'chunked':
711 length = int(self.headers.getheader('content-length'))
712 return self.rfile.read(length)
713
714 # Read the request body as chunks.
715 body = ""
716 while True:
717 line = self.rfile.readline()
718 length = int(line, 16)
719 if length == 0:
720 self.rfile.readline()
721 break
722 body += self.rfile.read(length)
723 self.rfile.read(2)
724 return body
725
initial.commit94958cf2008-07-26 22:42:52 +0000726 def EchoHandler(self):
727 """This handler just echoes back the payload of the request, for testing
728 form submission."""
729
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000730 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000731 return False
732
733 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000734 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000735 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000736 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000737 return True
738
739 def EchoTitleHandler(self):
740 """This handler is like Echo, but sets the page title to the request."""
741
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000742 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000743 return False
744
745 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000746 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000747 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000748 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000749 self.wfile.write('<html><head><title>')
750 self.wfile.write(request)
751 self.wfile.write('</title></head></html>')
752 return True
753
754 def EchoAllHandler(self):
755 """This handler yields a (more) human-readable page listing information
756 about the request header & contents."""
757
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000758 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000759 return False
760
761 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000762 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000763 self.end_headers()
764 self.wfile.write('<html><head><style>'
765 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
766 '</style></head><body>'
767 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000768 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000769 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000770
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000771 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000772 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000773 params = cgi.parse_qs(qs, keep_blank_values=1)
774
775 for param in params:
776 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000777
778 self.wfile.write('</pre>')
779
780 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
781
782 self.wfile.write('</body></html>')
783 return True
784
785 def DownloadHandler(self):
786 """This handler sends a downloadable file with or without reporting
787 the size (6K)."""
788
789 if self.path.startswith("/download-unknown-size"):
790 send_length = False
791 elif self.path.startswith("/download-known-size"):
792 send_length = True
793 else:
794 return False
795
796 #
797 # The test which uses this functionality is attempting to send
798 # small chunks of data to the client. Use a fairly large buffer
799 # so that we'll fill chrome's IO buffer enough to force it to
800 # actually write the data.
801 # See also the comments in the client-side of this test in
802 # download_uitest.cc
803 #
804 size_chunk1 = 35*1024
805 size_chunk2 = 10*1024
806
807 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000808 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000809 self.send_header('Cache-Control', 'max-age=0')
810 if send_length:
811 self.send_header('Content-Length', size_chunk1 + size_chunk2)
812 self.end_headers()
813
814 # First chunk of data:
815 self.wfile.write("*" * size_chunk1)
816 self.wfile.flush()
817
818 # handle requests until one of them clears this flag.
819 self.server.waitForDownload = True
820 while self.server.waitForDownload:
821 self.server.handle_request()
822
823 # Second chunk of data:
824 self.wfile.write("*" * size_chunk2)
825 return True
826
827 def DownloadFinishHandler(self):
828 """This handler just tells the server to finish the current download."""
829
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000830 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000831 return False
832
833 self.server.waitForDownload = False
834 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000835 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000836 self.send_header('Cache-Control', 'max-age=0')
837 self.end_headers()
838 return True
839
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000840 def _ReplaceFileData(self, data, query_parameters):
841 """Replaces matching substrings in a file.
842
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000843 If the 'replace_text' URL query parameter is present, it is expected to be
844 of the form old_text:new_text, which indicates that any old_text strings in
845 the file are replaced with new_text. Multiple 'replace_text' parameters may
846 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000847
848 If the parameters are not present, |data| is returned.
849 """
850 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000851 replace_text_values = query_dict.get('replace_text', [])
852 for replace_text_value in replace_text_values:
853 replace_text_args = replace_text_value.split(':')
854 if len(replace_text_args) != 2:
855 raise ValueError(
856 'replace_text must be of form old_text:new_text. Actual value: %s' %
857 replace_text_value)
858 old_text_b64, new_text_b64 = replace_text_args
859 old_text = base64.urlsafe_b64decode(old_text_b64)
860 new_text = base64.urlsafe_b64decode(new_text_b64)
861 data = data.replace(old_text, new_text)
862 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000863
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000864 def ZipFileHandler(self):
865 """This handler sends the contents of the requested file in compressed form.
866 Can pass in a parameter that specifies that the content length be
867 C - the compressed size (OK),
868 U - the uncompressed size (Non-standard, but handled),
869 S - less than compressed (OK because we keep going),
870 M - larger than compressed but less than uncompressed (an error),
871 L - larger than uncompressed (an error)
872 Example: compressedfiles/Picture_1.doc?C
873 """
874
875 prefix = "/compressedfiles/"
876 if not self.path.startswith(prefix):
877 return False
878
879 # Consume a request body if present.
880 if self.command == 'POST' or self.command == 'PUT' :
881 self.ReadRequestBody()
882
883 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
884
885 if not query in ('C', 'U', 'S', 'M', 'L'):
886 return False
887
888 sub_path = url_path[len(prefix):]
889 entries = sub_path.split('/')
890 file_path = os.path.join(self.server.data_dir, *entries)
891 if os.path.isdir(file_path):
892 file_path = os.path.join(file_path, 'index.html')
893
894 if not os.path.isfile(file_path):
895 print "File not found " + sub_path + " full path:" + file_path
896 self.send_error(404)
897 return True
898
899 f = open(file_path, "rb")
900 data = f.read()
901 uncompressed_len = len(data)
902 f.close()
903
904 # Compress the data.
905 data = zlib.compress(data)
906 compressed_len = len(data)
907
908 content_length = compressed_len
909 if query == 'U':
910 content_length = uncompressed_len
911 elif query == 'S':
912 content_length = compressed_len / 2
913 elif query == 'M':
914 content_length = (compressed_len + uncompressed_len) / 2
915 elif query == 'L':
916 content_length = compressed_len + uncompressed_len
917
918 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000919 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000920 self.send_header('Content-encoding', 'deflate')
921 self.send_header('Connection', 'close')
922 self.send_header('Content-Length', content_length)
923 self.send_header('ETag', '\'' + file_path + '\'')
924 self.end_headers()
925
926 self.wfile.write(data)
927
928 return True
929
initial.commit94958cf2008-07-26 22:42:52 +0000930 def FileHandler(self):
931 """This handler sends the contents of the requested file. Wow, it's like
932 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000933 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000934 if not self.path.startswith(prefix):
935 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000936 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000937
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000938 def PostOnlyFileHandler(self):
939 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000940 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000941 if not self.path.startswith(prefix):
942 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000943 return self._FileHandlerHelper(prefix)
944
945 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000946 request_body = ''
947 if self.command == 'POST' or self.command == 'PUT':
948 # Consume a request body if present.
949 request_body = self.ReadRequestBody()
950
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000951 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000952 query_dict = cgi.parse_qs(query)
953
954 expected_body = query_dict.get('expected_body', [])
955 if expected_body and request_body not in expected_body:
956 self.send_response(404)
957 self.end_headers()
958 self.wfile.write('')
959 return True
960
961 expected_headers = query_dict.get('expected_headers', [])
962 for expected_header in expected_headers:
963 header_name, expected_value = expected_header.split(':')
964 if self.headers.getheader(header_name) != expected_value:
965 self.send_response(404)
966 self.end_headers()
967 self.wfile.write('')
968 return True
969
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000970 sub_path = url_path[len(prefix):]
971 entries = sub_path.split('/')
972 file_path = os.path.join(self.server.data_dir, *entries)
973 if os.path.isdir(file_path):
974 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000975
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000976 if not os.path.isfile(file_path):
977 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000978 self.send_error(404)
979 return True
980
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000981 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000982 data = f.read()
983 f.close()
984
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000985 data = self._ReplaceFileData(data, query)
986
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000987 old_protocol_version = self.protocol_version
988
initial.commit94958cf2008-07-26 22:42:52 +0000989 # If file.mock-http-headers exists, it contains the headers we
990 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000991 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000992 if os.path.isfile(headers_path):
993 f = open(headers_path, "r")
994
995 # "HTTP/1.1 200 OK"
996 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000997 http_major, http_minor, status_code = re.findall(
998 'HTTP/(\d+).(\d+) (\d+)', response)[0]
999 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001000 self.send_response(int(status_code))
1001
1002 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001003 header_values = re.findall('(\S+):\s*(.*)', line)
1004 if len(header_values) > 0:
1005 # "name: value"
1006 name, value = header_values[0]
1007 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001008 f.close()
1009 else:
1010 # Could be more generic once we support mime-type sniffing, but for
1011 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001012
1013 range = self.headers.get('Range')
1014 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001015 # Note this doesn't handle all valid byte range values (i.e. left
1016 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001017 range = range[6:].split('-')
1018 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001019 if range[1]:
1020 end = int(range[1])
1021 else:
1022 end = len(data)
jam@chromium.org41550782010-11-17 23:47:50 +00001023
1024 self.send_response(206)
1025 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1026 str(len(data))
1027 self.send_header('Content-Range', content_range)
1028 data = data[start: end + 1]
1029 else:
1030 self.send_response(200)
1031
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001032 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001033 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001034 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001035 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001036 self.end_headers()
1037
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001038 if (self.command != 'HEAD'):
1039 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001040
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001041 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001042 return True
1043
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001044 def SetCookieHandler(self):
1045 """This handler just sets a cookie, for testing cookie handling."""
1046
1047 if not self._ShouldHandleRequest("/set-cookie"):
1048 return False
1049
1050 query_char = self.path.find('?')
1051 if query_char != -1:
1052 cookie_values = self.path[query_char + 1:].split('&')
1053 else:
1054 cookie_values = ("",)
1055 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001056 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001057 for cookie_value in cookie_values:
1058 self.send_header('Set-Cookie', '%s' % cookie_value)
1059 self.end_headers()
1060 for cookie_value in cookie_values:
1061 self.wfile.write('%s' % cookie_value)
1062 return True
1063
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001064 def SetHeaderHandler(self):
1065 """This handler sets a response header. Parameters are in the
1066 key%3A%20value&key2%3A%20value2 format."""
1067
1068 if not self._ShouldHandleRequest("/set-header"):
1069 return False
1070
1071 query_char = self.path.find('?')
1072 if query_char != -1:
1073 headers_values = self.path[query_char + 1:].split('&')
1074 else:
1075 headers_values = ("",)
1076 self.send_response(200)
1077 self.send_header('Content-Type', 'text/html')
1078 for header_value in headers_values:
1079 header_value = urllib.unquote(header_value)
1080 (key, value) = header_value.split(': ', 1)
1081 self.send_header(key, value)
1082 self.end_headers()
1083 for header_value in headers_values:
1084 self.wfile.write('%s' % header_value)
1085 return True
1086
initial.commit94958cf2008-07-26 22:42:52 +00001087 def AuthBasicHandler(self):
1088 """This handler tests 'Basic' authentication. It just sends a page with
1089 title 'user/pass' if you succeed."""
1090
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001091 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001092 return False
1093
1094 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001095 expected_password = 'secret'
1096 realm = 'testrealm'
1097 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001098
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001099 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1100 query_params = cgi.parse_qs(query, True)
1101 if 'set-cookie-if-challenged' in query_params:
1102 set_cookie_if_challenged = True
1103 if 'password' in query_params:
1104 expected_password = query_params['password'][0]
1105 if 'realm' in query_params:
1106 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001107
initial.commit94958cf2008-07-26 22:42:52 +00001108 auth = self.headers.getheader('authorization')
1109 try:
1110 if not auth:
1111 raise Exception('no auth')
1112 b64str = re.findall(r'Basic (\S+)', auth)[0]
1113 userpass = base64.b64decode(b64str)
1114 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001115 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001116 raise Exception('wrong password')
1117 except Exception, e:
1118 # Authentication failed.
1119 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001120 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001121 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001122 if set_cookie_if_challenged:
1123 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001124 self.end_headers()
1125 self.wfile.write('<html><head>')
1126 self.wfile.write('<title>Denied: %s</title>' % e)
1127 self.wfile.write('</head><body>')
1128 self.wfile.write('auth=%s<p>' % auth)
1129 self.wfile.write('b64str=%s<p>' % b64str)
1130 self.wfile.write('username: %s<p>' % username)
1131 self.wfile.write('userpass: %s<p>' % userpass)
1132 self.wfile.write('password: %s<p>' % password)
1133 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1134 self.wfile.write('</body></html>')
1135 return True
1136
1137 # Authentication successful. (Return a cachable response to allow for
1138 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001139 old_protocol_version = self.protocol_version
1140 self.protocol_version = "HTTP/1.1"
1141
initial.commit94958cf2008-07-26 22:42:52 +00001142 if_none_match = self.headers.getheader('if-none-match')
1143 if if_none_match == "abc":
1144 self.send_response(304)
1145 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001146 elif url_path.endswith(".gif"):
1147 # Using chrome/test/data/google/logo.gif as the test image
1148 test_image_path = ['google', 'logo.gif']
1149 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1150 if not os.path.isfile(gif_path):
1151 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001152 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001153 return True
1154
1155 f = open(gif_path, "rb")
1156 data = f.read()
1157 f.close()
1158
1159 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001160 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001161 self.send_header('Cache-control', 'max-age=60000')
1162 self.send_header('Etag', 'abc')
1163 self.end_headers()
1164 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001165 else:
1166 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001167 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001168 self.send_header('Cache-control', 'max-age=60000')
1169 self.send_header('Etag', 'abc')
1170 self.end_headers()
1171 self.wfile.write('<html><head>')
1172 self.wfile.write('<title>%s/%s</title>' % (username, password))
1173 self.wfile.write('</head><body>')
1174 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001175 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001176 self.wfile.write('</body></html>')
1177
rvargas@google.com54453b72011-05-19 01:11:11 +00001178 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001179 return True
1180
tonyg@chromium.org75054202010-03-31 22:06:10 +00001181 def GetNonce(self, force_reset=False):
1182 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001183
tonyg@chromium.org75054202010-03-31 22:06:10 +00001184 This is a fake implementation. A real implementation would only use a given
1185 nonce a single time (hence the name n-once). However, for the purposes of
1186 unittesting, we don't care about the security of the nonce.
1187
1188 Args:
1189 force_reset: Iff set, the nonce will be changed. Useful for testing the
1190 "stale" response.
1191 """
1192 if force_reset or not self.server.nonce_time:
1193 self.server.nonce_time = time.time()
1194 return _new_md5('privatekey%s%d' %
1195 (self.path, self.server.nonce_time)).hexdigest()
1196
1197 def AuthDigestHandler(self):
1198 """This handler tests 'Digest' authentication.
1199
1200 It just sends a page with title 'user/pass' if you succeed.
1201
1202 A stale response is sent iff "stale" is present in the request path.
1203 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001204 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001205 return False
1206
tonyg@chromium.org75054202010-03-31 22:06:10 +00001207 stale = 'stale' in self.path
1208 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001209 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001210 password = 'secret'
1211 realm = 'testrealm'
1212
1213 auth = self.headers.getheader('authorization')
1214 pairs = {}
1215 try:
1216 if not auth:
1217 raise Exception('no auth')
1218 if not auth.startswith('Digest'):
1219 raise Exception('not digest')
1220 # Pull out all the name="value" pairs as a dictionary.
1221 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1222
1223 # Make sure it's all valid.
1224 if pairs['nonce'] != nonce:
1225 raise Exception('wrong nonce')
1226 if pairs['opaque'] != opaque:
1227 raise Exception('wrong opaque')
1228
1229 # Check the 'response' value and make sure it matches our magic hash.
1230 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001231 hash_a1 = _new_md5(
1232 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001233 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001234 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001235 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001236 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1237 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001238 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001239
1240 if pairs['response'] != response:
1241 raise Exception('wrong password')
1242 except Exception, e:
1243 # Authentication failed.
1244 self.send_response(401)
1245 hdr = ('Digest '
1246 'realm="%s", '
1247 'domain="/", '
1248 'qop="auth", '
1249 'algorithm=MD5, '
1250 'nonce="%s", '
1251 'opaque="%s"') % (realm, nonce, opaque)
1252 if stale:
1253 hdr += ', stale="TRUE"'
1254 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001255 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001256 self.end_headers()
1257 self.wfile.write('<html><head>')
1258 self.wfile.write('<title>Denied: %s</title>' % e)
1259 self.wfile.write('</head><body>')
1260 self.wfile.write('auth=%s<p>' % auth)
1261 self.wfile.write('pairs=%s<p>' % pairs)
1262 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1263 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1264 self.wfile.write('</body></html>')
1265 return True
1266
1267 # Authentication successful.
1268 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001269 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001270 self.end_headers()
1271 self.wfile.write('<html><head>')
1272 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1273 self.wfile.write('</head><body>')
1274 self.wfile.write('auth=%s<p>' % auth)
1275 self.wfile.write('pairs=%s<p>' % pairs)
1276 self.wfile.write('</body></html>')
1277
1278 return True
1279
1280 def SlowServerHandler(self):
1281 """Wait for the user suggested time before responding. The syntax is
1282 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001283 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001284 return False
1285 query_char = self.path.find('?')
1286 wait_sec = 1.0
1287 if query_char >= 0:
1288 try:
1289 wait_sec = int(self.path[query_char + 1:])
1290 except ValueError:
1291 pass
1292 time.sleep(wait_sec)
1293 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001294 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001295 self.end_headers()
1296 self.wfile.write("waited %d seconds" % wait_sec)
1297 return True
1298
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001299 def ChunkedServerHandler(self):
1300 """Send chunked response. Allows to specify chunks parameters:
1301 - waitBeforeHeaders - ms to wait before sending headers
1302 - waitBetweenChunks - ms to wait between chunks
1303 - chunkSize - size of each chunk in bytes
1304 - chunksNumber - number of chunks
1305 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1306 waits one second, then sends headers and five chunks five bytes each."""
1307 if not self._ShouldHandleRequest("/chunked"):
1308 return False
1309 query_char = self.path.find('?')
1310 chunkedSettings = {'waitBeforeHeaders' : 0,
1311 'waitBetweenChunks' : 0,
1312 'chunkSize' : 5,
1313 'chunksNumber' : 5}
1314 if query_char >= 0:
1315 params = self.path[query_char + 1:].split('&')
1316 for param in params:
1317 keyValue = param.split('=')
1318 if len(keyValue) == 2:
1319 try:
1320 chunkedSettings[keyValue[0]] = int(keyValue[1])
1321 except ValueError:
1322 pass
1323 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1324 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1325 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001326 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001327 self.send_header('Connection', 'close')
1328 self.send_header('Transfer-Encoding', 'chunked')
1329 self.end_headers()
1330 # Chunked encoding: sending all chunks, then final zero-length chunk and
1331 # then final CRLF.
1332 for i in range(0, chunkedSettings['chunksNumber']):
1333 if i > 0:
1334 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1335 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1336 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1337 self.sendChunkHelp('')
1338 return True
1339
initial.commit94958cf2008-07-26 22:42:52 +00001340 def ContentTypeHandler(self):
1341 """Returns a string of html with the given content type. E.g.,
1342 /contenttype?text/css returns an html file with the Content-Type
1343 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001344 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001345 return False
1346 query_char = self.path.find('?')
1347 content_type = self.path[query_char + 1:].strip()
1348 if not content_type:
1349 content_type = 'text/html'
1350 self.send_response(200)
1351 self.send_header('Content-Type', content_type)
1352 self.end_headers()
1353 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1354 return True
1355
creis@google.com2f4f6a42011-03-25 19:44:19 +00001356 def NoContentHandler(self):
1357 """Returns a 204 No Content response."""
1358 if not self._ShouldHandleRequest("/nocontent"):
1359 return False
1360 self.send_response(204)
1361 self.end_headers()
1362 return True
1363
initial.commit94958cf2008-07-26 22:42:52 +00001364 def ServerRedirectHandler(self):
1365 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001366 '/server-redirect?http://foo.bar/asdf' to redirect to
1367 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001368
1369 test_name = "/server-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(301) # moved permanently
1380 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001381 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001382 self.end_headers()
1383 self.wfile.write('<html><head>')
1384 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1385
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001386 return True
initial.commit94958cf2008-07-26 22:42:52 +00001387
1388 def ClientRedirectHandler(self):
1389 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001390 '/client-redirect?http://foo.bar/asdf' to redirect to
1391 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001392
1393 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001394 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001395 return False
1396
1397 query_char = self.path.find('?');
1398 if query_char < 0 or len(self.path) <= query_char + 1:
1399 self.sendRedirectHelp(test_name)
1400 return True
1401 dest = self.path[query_char + 1:]
1402
1403 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001404 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001405 self.end_headers()
1406 self.wfile.write('<html><head>')
1407 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1408 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1409
1410 return True
1411
tony@chromium.org03266982010-03-05 03:18:42 +00001412 def MultipartHandler(self):
1413 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001414 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001415 if not self._ShouldHandleRequest(test_name):
1416 return False
1417
1418 num_frames = 10
1419 bound = '12345'
1420 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001421 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +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.org03266982010-03-05 03:18:42 +00001428 self.wfile.write('<title>page ' + str(i) + '</title>')
1429 self.wfile.write('page ' + str(i))
1430
1431 self.wfile.write('--' + bound + '--')
1432 return True
1433
tony@chromium.org4cb88302011-09-27 22:13:49 +00001434 def MultipartSlowHandler(self):
1435 """Send a multipart response (3 text/html pages) with a slight delay
1436 between each page. This is similar to how some pages show status using
1437 multipart."""
1438 test_name = '/multipart-slow'
1439 if not self._ShouldHandleRequest(test_name):
1440 return False
1441
1442 num_frames = 3
1443 bound = '12345'
1444 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001445 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001446 'multipart/x-mixed-replace;boundary=' + bound)
1447 self.end_headers()
1448
1449 for i in xrange(num_frames):
1450 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001451 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001452 time.sleep(0.25)
1453 if i == 2:
1454 self.wfile.write('<title>PASS</title>')
1455 else:
1456 self.wfile.write('<title>page ' + str(i) + '</title>')
1457 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1458
1459 self.wfile.write('--' + bound + '--')
1460 return True
1461
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001462 def GetSSLSessionCacheHandler(self):
1463 """Send a reply containing a log of the session cache operations."""
1464
1465 if not self._ShouldHandleRequest('/ssl-session-cache'):
1466 return False
1467
1468 self.send_response(200)
1469 self.send_header('Content-Type', 'text/plain')
1470 self.end_headers()
1471 try:
1472 for (action, sessionID) in self.server.session_cache.log:
1473 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1474 except AttributeError, e:
1475 self.wfile.write('Pass --https-record-resume in order to use' +
1476 ' this request')
1477 return True
1478
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001479 def CloseSocketHandler(self):
1480 """Closes the socket without sending anything."""
1481
1482 if not self._ShouldHandleRequest('/close-socket'):
1483 return False
1484
1485 self.wfile.close()
1486 return True
1487
initial.commit94958cf2008-07-26 22:42:52 +00001488 def DefaultResponseHandler(self):
1489 """This is the catch-all response handler for requests that aren't handled
1490 by one of the special handlers above.
1491 Note that we specify the content-length as without it the https connection
1492 is not closed properly (and the browser keeps expecting data)."""
1493
1494 contents = "Default response given for path: " + self.path
1495 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001496 self.send_header('Content-Type', 'text/html')
1497 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001498 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001499 if (self.command != 'HEAD'):
1500 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001501 return True
1502
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001503 def RedirectConnectHandler(self):
1504 """Sends a redirect to the CONNECT request for www.redirect.com. This
1505 response is not specified by the RFC, so the browser should not follow
1506 the redirect."""
1507
1508 if (self.path.find("www.redirect.com") < 0):
1509 return False
1510
1511 dest = "http://www.destination.com/foo.js"
1512
1513 self.send_response(302) # moved temporarily
1514 self.send_header('Location', dest)
1515 self.send_header('Connection', 'close')
1516 self.end_headers()
1517 return True
1518
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001519 def ServerAuthConnectHandler(self):
1520 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1521 response doesn't make sense because the proxy server cannot request
1522 server authentication."""
1523
1524 if (self.path.find("www.server-auth.com") < 0):
1525 return False
1526
1527 challenge = 'Basic realm="WallyWorld"'
1528
1529 self.send_response(401) # unauthorized
1530 self.send_header('WWW-Authenticate', challenge)
1531 self.send_header('Connection', 'close')
1532 self.end_headers()
1533 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001534
1535 def DefaultConnectResponseHandler(self):
1536 """This is the catch-all response handler for CONNECT requests that aren't
1537 handled by one of the special handlers above. Real Web servers respond
1538 with 400 to CONNECT requests."""
1539
1540 contents = "Your client has issued a malformed or illegal request."
1541 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001542 self.send_header('Content-Type', 'text/html')
1543 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001544 self.end_headers()
1545 self.wfile.write(contents)
1546 return True
1547
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001548 def DeviceManagementHandler(self):
1549 """Delegates to the device management service used for cloud policy."""
1550 if not self._ShouldHandleRequest("/device_management"):
1551 return False
1552
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001553 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001554
1555 if not self.server._device_management_handler:
1556 import device_management
1557 policy_path = os.path.join(self.server.data_dir, 'device_management')
1558 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001559 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001560 self.server.policy_keys,
1561 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001562
1563 http_response, raw_reply = (
1564 self.server._device_management_handler.HandleRequest(self.path,
1565 self.headers,
1566 raw_request))
1567 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001568 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001569 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001570 self.end_headers()
1571 self.wfile.write(raw_reply)
1572 return True
1573
initial.commit94958cf2008-07-26 22:42:52 +00001574 # called by the redirect handling function when there is no parameter
1575 def sendRedirectHelp(self, redirect_name):
1576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001577 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001578 self.end_headers()
1579 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1580 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1581 self.wfile.write('</body></html>')
1582
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001583 # called by chunked handling function
1584 def sendChunkHelp(self, chunk):
1585 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1586 self.wfile.write('%X\r\n' % len(chunk))
1587 self.wfile.write(chunk)
1588 self.wfile.write('\r\n')
1589
akalin@chromium.org154bb132010-11-12 02:20:27 +00001590
1591class SyncPageHandler(BasePageHandler):
1592 """Handler for the main HTTP sync server."""
1593
1594 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001595 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001596 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001597 self.ChromiumSyncDisableNotificationsOpHandler,
1598 self.ChromiumSyncEnableNotificationsOpHandler,
1599 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001600 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001601 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001602 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001603 self.ChromiumSyncErrorOpHandler,
1604 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001605
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001606 post_handlers = [self.ChromiumSyncCommandHandler,
1607 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001608 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001609 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001610 post_handlers, [])
1611
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001612
akalin@chromium.org154bb132010-11-12 02:20:27 +00001613 def ChromiumSyncTimeHandler(self):
1614 """Handle Chromium sync .../time requests.
1615
1616 The syncer sometimes checks server reachability by examining /time.
1617 """
1618 test_name = "/chromiumsync/time"
1619 if not self._ShouldHandleRequest(test_name):
1620 return False
1621
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001622 # Chrome hates it if we send a response before reading the request.
1623 if self.headers.getheader('content-length'):
1624 length = int(self.headers.getheader('content-length'))
1625 raw_request = self.rfile.read(length)
1626
akalin@chromium.org154bb132010-11-12 02:20:27 +00001627 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001628 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001629 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001630 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001631 return True
1632
1633 def ChromiumSyncCommandHandler(self):
1634 """Handle a chromiumsync command arriving via http.
1635
1636 This covers all sync protocol commands: authentication, getupdates, and
1637 commit.
1638 """
1639 test_name = "/chromiumsync/command"
1640 if not self._ShouldHandleRequest(test_name):
1641 return False
1642
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001643 length = int(self.headers.getheader('content-length'))
1644 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001645 http_response = 200
1646 raw_reply = None
1647 if not self.server.GetAuthenticated():
1648 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001649 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1650 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001651 else:
1652 http_response, raw_reply = self.server.HandleCommand(
1653 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001654
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001655 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001656 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001657 if http_response == 401:
1658 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001659 self.end_headers()
1660 self.wfile.write(raw_reply)
1661 return True
1662
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001663 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001664 test_name = "/chromiumsync/migrate"
1665 if not self._ShouldHandleRequest(test_name):
1666 return False
1667
1668 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1669 self.path)
1670 self.send_response(http_response)
1671 self.send_header('Content-Type', 'text/html')
1672 self.send_header('Content-Length', len(raw_reply))
1673 self.end_headers()
1674 self.wfile.write(raw_reply)
1675 return True
1676
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001677 def ChromiumSyncCredHandler(self):
1678 test_name = "/chromiumsync/cred"
1679 if not self._ShouldHandleRequest(test_name):
1680 return False
1681 try:
1682 query = urlparse.urlparse(self.path)[4]
1683 cred_valid = urlparse.parse_qs(query)['valid']
1684 if cred_valid[0] == 'True':
1685 self.server.SetAuthenticated(True)
1686 else:
1687 self.server.SetAuthenticated(False)
1688 except:
1689 self.server.SetAuthenticated(False)
1690
1691 http_response = 200
1692 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1693 self.send_response(http_response)
1694 self.send_header('Content-Type', 'text/html')
1695 self.send_header('Content-Length', len(raw_reply))
1696 self.end_headers()
1697 self.wfile.write(raw_reply)
1698 return True
1699
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001700 def ChromiumSyncDisableNotificationsOpHandler(self):
1701 test_name = "/chromiumsync/disablenotifications"
1702 if not self._ShouldHandleRequest(test_name):
1703 return False
1704 self.server.GetXmppServer().DisableNotifications()
1705 result = 200
1706 raw_reply = ('<html><title>Notifications disabled</title>'
1707 '<H1>Notifications disabled</H1></html>')
1708 self.send_response(result)
1709 self.send_header('Content-Type', 'text/html')
1710 self.send_header('Content-Length', len(raw_reply))
1711 self.end_headers()
1712 self.wfile.write(raw_reply)
1713 return True;
1714
1715 def ChromiumSyncEnableNotificationsOpHandler(self):
1716 test_name = "/chromiumsync/enablenotifications"
1717 if not self._ShouldHandleRequest(test_name):
1718 return False
1719 self.server.GetXmppServer().EnableNotifications()
1720 result = 200
1721 raw_reply = ('<html><title>Notifications enabled</title>'
1722 '<H1>Notifications enabled</H1></html>')
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
1730 def ChromiumSyncSendNotificationOpHandler(self):
1731 test_name = "/chromiumsync/sendnotification"
1732 if not self._ShouldHandleRequest(test_name):
1733 return False
1734 query = urlparse.urlparse(self.path)[4]
1735 query_params = urlparse.parse_qs(query)
1736 channel = ''
1737 data = ''
1738 if 'channel' in query_params:
1739 channel = query_params['channel'][0]
1740 if 'data' in query_params:
1741 data = query_params['data'][0]
1742 self.server.GetXmppServer().SendNotification(channel, data)
1743 result = 200
1744 raw_reply = ('<html><title>Notification sent</title>'
1745 '<H1>Notification sent with channel "%s" '
1746 'and data "%s"</H1></html>'
1747 % (channel, data))
1748 self.send_response(result)
1749 self.send_header('Content-Type', 'text/html')
1750 self.send_header('Content-Length', len(raw_reply))
1751 self.end_headers()
1752 self.wfile.write(raw_reply)
1753 return True;
1754
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001755 def ChromiumSyncBirthdayErrorOpHandler(self):
1756 test_name = "/chromiumsync/birthdayerror"
1757 if not self._ShouldHandleRequest(test_name):
1758 return False
1759 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
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
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001767 def ChromiumSyncTransientErrorOpHandler(self):
1768 test_name = "/chromiumsync/transienterror"
1769 if not self._ShouldHandleRequest(test_name):
1770 return False
1771 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
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
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001779 def ChromiumSyncErrorOpHandler(self):
1780 test_name = "/chromiumsync/error"
1781 if not self._ShouldHandleRequest(test_name):
1782 return False
1783 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1784 self.path)
1785 self.send_response(result)
1786 self.send_header('Content-Type', 'text/html')
1787 self.send_header('Content-Length', len(raw_reply))
1788 self.end_headers()
1789 self.wfile.write(raw_reply)
1790 return True;
1791
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001792 def ChromiumSyncSyncTabsOpHandler(self):
1793 test_name = "/chromiumsync/synctabs"
1794 if not self._ShouldHandleRequest(test_name):
1795 return False
1796 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1797 self.send_response(result)
1798 self.send_header('Content-Type', 'text/html')
1799 self.send_header('Content-Length', len(raw_reply))
1800 self.end_headers()
1801 self.wfile.write(raw_reply)
1802 return True;
1803
akalin@chromium.org154bb132010-11-12 02:20:27 +00001804
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001805def MakeDataDir():
1806 if options.data_dir:
1807 if not os.path.isdir(options.data_dir):
1808 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1809 return None
1810 my_data_dir = options.data_dir
1811 else:
1812 # Create the default path to our data dir, relative to the exe dir.
1813 my_data_dir = os.path.dirname(sys.argv[0])
1814 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001815 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001816
1817 #TODO(ibrar): Must use Find* funtion defined in google\tools
1818 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1819
1820 return my_data_dir
1821
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001822
1823class TCPEchoHandler(SocketServer.BaseRequestHandler):
1824 """The RequestHandler class for TCP echo server.
1825
1826 It is instantiated once per connection to the server, and overrides the
1827 handle() method to implement communication to the client.
1828 """
1829
1830 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001831 """Handles the request from the client and constructs a response."""
1832
1833 data = self.request.recv(65536).strip()
1834 # Verify the "echo request" message received from the client. Send back
1835 # "echo response" message if "echo request" message is valid.
1836 try:
1837 return_data = echo_message.GetEchoResponseData(data)
1838 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001839 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001840 except ValueError:
1841 return
1842
1843 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001844
1845
1846class UDPEchoHandler(SocketServer.BaseRequestHandler):
1847 """The RequestHandler class for UDP echo server.
1848
1849 It is instantiated once per connection to the server, and overrides the
1850 handle() method to implement communication to the client.
1851 """
1852
1853 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001854 """Handles the request from the client and constructs a response."""
1855
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001856 data = self.request[0].strip()
1857 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001858 # Verify the "echo request" message received from the client. Send back
1859 # "echo response" message if "echo request" message is valid.
1860 try:
1861 return_data = echo_message.GetEchoResponseData(data)
1862 if not return_data:
1863 return
1864 except ValueError:
1865 return
1866 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001867
1868
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001869class FileMultiplexer:
1870 def __init__(self, fd1, fd2) :
1871 self.__fd1 = fd1
1872 self.__fd2 = fd2
1873
1874 def __del__(self) :
1875 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1876 self.__fd1.close()
1877 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1878 self.__fd2.close()
1879
1880 def write(self, text) :
1881 self.__fd1.write(text)
1882 self.__fd2.write(text)
1883
1884 def flush(self) :
1885 self.__fd1.flush()
1886 self.__fd2.flush()
1887
initial.commit94958cf2008-07-26 22:42:52 +00001888def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001889 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001890 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001891 if options.log_to_console:
1892 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1893 else:
1894 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001895
1896 port = options.port
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001897 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00001898
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001899 server_data = {}
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001900 server_data['host'] = host
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001901
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001902 if options.server_type == SERVER_HTTP:
1903 if options.cert:
1904 # let's make sure the cert file exists.
1905 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001906 print 'specified server cert file not found: ' + options.cert + \
1907 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001908 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001909 for ca_cert in options.ssl_client_ca:
1910 if not os.path.isfile(ca_cert):
1911 print 'specified trusted client CA file not found: ' + ca_cert + \
1912 ' exiting...'
1913 return
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001914 server = HTTPSServer((host, port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001915 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001916 options.ssl_bulk_cipher, options.record_resume)
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001917 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001918 else:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001919 server = HTTPServer((host, port), TestPageHandler)
1920 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00001921
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001922 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001923 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001924 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001925 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001926 server.policy_keys = options.policy_keys
1927 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001928 elif options.server_type == SERVER_SYNC:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001929 server = SyncHTTPServer((host, port), SyncPageHandler)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001930 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001931 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1932 server_data['port'] = server.server_port
1933 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001934 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001935 # Used for generating the key (randomly) that encodes the "echo request"
1936 # message.
1937 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001938 server = TCPEchoServer((host, port), TCPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001939 print 'Echo TCP server started on port %d...' % server.server_port
1940 server_data['port'] = server.server_port
1941 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001942 # Used for generating the key (randomly) that encodes the "echo request"
1943 # message.
1944 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001945 server = UDPEchoServer((host, port), UDPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001946 print 'Echo UDP server started on port %d...' % server.server_port
1947 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001948 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001949 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001950 my_data_dir = MakeDataDir()
1951
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001952 # Instantiate a dummy authorizer for managing 'virtual' users
1953 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1954
1955 # Define a new user having full r/w permissions and a read-only
1956 # anonymous user
1957 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1958
1959 authorizer.add_anonymous(my_data_dir)
1960
1961 # Instantiate FTP handler class
1962 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1963 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001964
1965 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001966 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1967 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001968
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001969 # Instantiate FTP server class and listen to address:port
1970 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001971 server_data['port'] = server.socket.getsockname()[1]
1972 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001973
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001974 # Notify the parent that we've started. (BaseServer subclasses
1975 # bind their sockets on construction.)
1976 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001977 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001978 server_data_len = len(server_data_json)
1979 print 'sending server_data: %s (%d bytes)' % (
1980 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001981 if sys.platform == 'win32':
1982 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1983 else:
1984 fd = options.startup_pipe
1985 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001986 # First write the data length as an unsigned 4-byte value. This
1987 # is _not_ using network byte ordering since the other end of the
1988 # pipe is on the same machine.
1989 startup_pipe.write(struct.pack('=L', server_data_len))
1990 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001991 startup_pipe.close()
1992
initial.commit94958cf2008-07-26 22:42:52 +00001993 try:
1994 server.serve_forever()
1995 except KeyboardInterrupt:
1996 print 'shutting down server'
1997 server.stop = True
1998
1999if __name__ == '__main__':
2000 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002001 option_parser.add_option("-f", '--ftp', action='store_const',
2002 const=SERVER_FTP, default=SERVER_HTTP,
2003 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00002004 help='start up an FTP server.')
2005 option_parser.add_option('', '--sync', action='store_const',
2006 const=SERVER_SYNC, default=SERVER_HTTP,
2007 dest='server_type',
2008 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002009 option_parser.add_option('', '--tcp-echo', action='store_const',
2010 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2011 dest='server_type',
2012 help='start up a tcp echo server.')
2013 option_parser.add_option('', '--udp-echo', action='store_const',
2014 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2015 dest='server_type',
2016 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002017 option_parser.add_option('', '--log-to-console', action='store_const',
2018 const=True, default=False,
2019 dest='log_to_console',
2020 help='Enables or disables sys.stdout logging to '
2021 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00002022 option_parser.add_option('', '--port', default='0', type='int',
2023 help='Port used by the server. If unspecified, the '
2024 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002025 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002026 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002027 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00002028 help='Specify that https should be used, specify '
2029 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002030 'the server should use.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002031 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2032 const=True, default=False, action='store_const',
2033 help='Record resumption cache events rather than'
2034 ' resuming as normal. Allows the use of the'
2035 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002036 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2037 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002038 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2039 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002040 'should include the CA named in the subject of '
2041 'the DER-encoded certificate contained in the '
2042 'specified file. This option may appear multiple '
2043 'times, indicating multiple CA names should be '
2044 'sent in the request.')
2045 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2046 help='Specify the bulk encryption algorithm(s)'
2047 'that will be accepted by the SSL server. Valid '
2048 'values are "aes256", "aes128", "3des", "rc4". If '
2049 'omitted, all algorithms will be used. This '
2050 'option may appear multiple times, indicating '
2051 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002052 option_parser.add_option('', '--file-root-url', default='/files/',
2053 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002054 option_parser.add_option('', '--startup-pipe', type='int',
2055 dest='startup_pipe',
2056 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002057 option_parser.add_option('', '--policy-key', action='append',
2058 dest='policy_keys',
2059 help='Specify a path to a PEM-encoded private key '
2060 'to use for policy signing. May be specified '
2061 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002062 'the server. If ther server has multiple keys, it '
2063 'will rotate through them in at each request a '
2064 'round-robin fashion. The server will generate a '
2065 'random key if none is specified on the command '
2066 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002067 option_parser.add_option('', '--policy-user', default='user@example.com',
2068 dest='policy_user',
2069 help='Specify the user name the server should '
2070 'report back to the client as the user owning the '
2071 'token used for making the policy request.')
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002072 option_parser.add_option('', '--host', default='127.0.0.1',
2073 dest='host',
2074 help='Hostname or IP upon which the server will '
2075 'listen. Client connections will also only be '
2076 'allowed from this address.')
initial.commit94958cf2008-07-26 22:42:52 +00002077 options, args = option_parser.parse_args()
2078
2079 sys.exit(main(options, args))