blob: f4442ed8d763074c8d97555cea7c8e74dedf99d4 [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()
123 x509 = tlslite.api.X509()
124 x509.parse(s)
125 self.cert_chain = tlslite.api.X509CertChain([x509])
126 s = open(cert_path).read()
127 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000128 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000129 self.ssl_client_cas = []
130 for ca_file in ssl_client_cas:
131 s = open(ca_file).read()
132 x509 = tlslite.api.X509()
133 x509.parse(s)
134 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000135 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
136 if ssl_bulk_ciphers is not None:
137 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000138
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000139 if record_resume_info:
140 # If record_resume_info is true then we'll replace the session cache with
141 # an object that records the lookups and inserts that it sees.
142 self.session_cache = RecordingSSLSessionCache()
143 else:
144 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000145 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
146
147 def handshake(self, tlsConnection):
148 """Creates the SSL connection."""
149 try:
150 tlsConnection.handshakeServer(certChain=self.cert_chain,
151 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000152 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000153 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000154 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000155 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000156 tlsConnection.ignoreAbruptClose = True
157 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000158 except tlslite.api.TLSAbruptCloseError:
159 # Ignore abrupt close.
160 return True
initial.commit94958cf2008-07-26 22:42:52 +0000161 except tlslite.api.TLSError, error:
162 print "Handshake failure:", str(error)
163 return False
164
akalin@chromium.org154bb132010-11-12 02:20:27 +0000165
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000166class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000167 """An HTTP server that handles sync commands."""
168
169 def __init__(self, server_address, request_handler_class):
170 # We import here to avoid pulling in chromiumsync's dependencies
171 # unless strictly necessary.
172 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000173 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000174 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000175 self._sync_handler = chromiumsync.TestServer()
176 self._xmpp_socket_map = {}
177 self._xmpp_server = xmppserver.XmppServer(
178 self._xmpp_socket_map, ('localhost', 0))
179 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000180 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000181
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000182 def GetXmppServer(self):
183 return self._xmpp_server
184
akalin@chromium.org154bb132010-11-12 02:20:27 +0000185 def HandleCommand(self, query, raw_request):
186 return self._sync_handler.HandleCommand(query, raw_request)
187
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000188 def HandleRequestNoBlock(self):
189 """Handles a single request.
190
191 Copied from SocketServer._handle_request_noblock().
192 """
193 try:
194 request, client_address = self.get_request()
195 except socket.error:
196 return
197 if self.verify_request(request, client_address):
198 try:
199 self.process_request(request, client_address)
200 except:
201 self.handle_error(request, client_address)
202 self.close_request(request)
203
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000204 def SetAuthenticated(self, auth_valid):
205 self.authenticated = auth_valid
206
207 def GetAuthenticated(self):
208 return self.authenticated
209
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000210 def serve_forever(self):
211 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
212 """
213
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000214 def HandleXmppSocket(fd, socket_map, handler):
215 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000216
217 Adapted from asyncore.read() et al.
218 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000219 xmpp_connection = socket_map.get(fd)
220 # This could happen if a previous handler call caused fd to get
221 # removed from socket_map.
222 if xmpp_connection is None:
223 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000224 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000225 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000226 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
227 raise
228 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000229 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000230
231 while True:
232 read_fds = [ self.fileno() ]
233 write_fds = []
234 exceptional_fds = []
235
236 for fd, xmpp_connection in self._xmpp_socket_map.items():
237 is_r = xmpp_connection.readable()
238 is_w = xmpp_connection.writable()
239 if is_r:
240 read_fds.append(fd)
241 if is_w:
242 write_fds.append(fd)
243 if is_r or is_w:
244 exceptional_fds.append(fd)
245
246 try:
247 read_fds, write_fds, exceptional_fds = (
248 select.select(read_fds, write_fds, exceptional_fds))
249 except select.error, err:
250 if err.args[0] != errno.EINTR:
251 raise
252 else:
253 continue
254
255 for fd in read_fds:
256 if fd == self.fileno():
257 self.HandleRequestNoBlock()
258 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000259 HandleXmppSocket(fd, self._xmpp_socket_map,
260 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000261
262 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000263 HandleXmppSocket(fd, self._xmpp_socket_map,
264 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000265
266 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000267 HandleXmppSocket(fd, self._xmpp_socket_map,
268 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000269
akalin@chromium.org154bb132010-11-12 02:20:27 +0000270
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000271class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
272 """This is a specialization of FTPServer that adds client verification."""
273
274 pass
275
276
277class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000278 """A TCP echo server that echoes back what it has received."""
279
280 def server_bind(self):
281 """Override server_bind to store the server name."""
282 SocketServer.TCPServer.server_bind(self)
283 host, port = self.socket.getsockname()[:2]
284 self.server_name = socket.getfqdn(host)
285 self.server_port = port
286
287 def serve_forever(self):
288 self.stop = False
289 self.nonce_time = None
290 while not self.stop:
291 self.handle_request()
292 self.socket.close()
293
294
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000295class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000296 """A UDP echo server that echoes back what it has received."""
297
298 def server_bind(self):
299 """Override server_bind to store the server name."""
300 SocketServer.UDPServer.server_bind(self)
301 host, port = self.socket.getsockname()[:2]
302 self.server_name = socket.getfqdn(host)
303 self.server_port = port
304
305 def serve_forever(self):
306 self.stop = False
307 self.nonce_time = None
308 while not self.stop:
309 self.handle_request()
310 self.socket.close()
311
312
akalin@chromium.org154bb132010-11-12 02:20:27 +0000313class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
314
315 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000316 connect_handlers, get_handlers, head_handlers, post_handlers,
317 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000318 self._connect_handlers = connect_handlers
319 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000320 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000321 self._post_handlers = post_handlers
322 self._put_handlers = put_handlers
323 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
324 self, request, client_address, socket_server)
325
326 def log_request(self, *args, **kwargs):
327 # Disable request logging to declutter test log output.
328 pass
329
330 def _ShouldHandleRequest(self, handler_name):
331 """Determines if the path can be handled by the handler.
332
333 We consider a handler valid if the path begins with the
334 handler name. It can optionally be followed by "?*", "/*".
335 """
336
337 pattern = re.compile('%s($|\?|/).*' % handler_name)
338 return pattern.match(self.path)
339
340 def do_CONNECT(self):
341 for handler in self._connect_handlers:
342 if handler():
343 return
344
345 def do_GET(self):
346 for handler in self._get_handlers:
347 if handler():
348 return
349
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000350 def do_HEAD(self):
351 for handler in self._head_handlers:
352 if handler():
353 return
354
akalin@chromium.org154bb132010-11-12 02:20:27 +0000355 def do_POST(self):
356 for handler in self._post_handlers:
357 if handler():
358 return
359
360 def do_PUT(self):
361 for handler in self._put_handlers:
362 if handler():
363 return
364
365
366class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000367
368 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000369 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000370 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000371 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000372 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000373 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000374 self.NoCacheMaxAgeTimeHandler,
375 self.NoCacheTimeHandler,
376 self.CacheTimeHandler,
377 self.CacheExpiresHandler,
378 self.CacheProxyRevalidateHandler,
379 self.CachePrivateHandler,
380 self.CachePublicHandler,
381 self.CacheSMaxAgeHandler,
382 self.CacheMustRevalidateHandler,
383 self.CacheMustRevalidateMaxAgeHandler,
384 self.CacheNoStoreHandler,
385 self.CacheNoStoreMaxAgeHandler,
386 self.CacheNoTransformHandler,
387 self.DownloadHandler,
388 self.DownloadFinishHandler,
389 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000390 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000391 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000392 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000393 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000394 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000395 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000396 self.AuthBasicHandler,
397 self.AuthDigestHandler,
398 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000399 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000400 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000401 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000402 self.ServerRedirectHandler,
403 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000404 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000405 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000406 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000407 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000408 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000409 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000410 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000411 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000412 self.DeviceManagementHandler,
413 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000414 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000415 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000416 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000417 head_handlers = [
418 self.FileHandler,
419 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000420
maruel@google.come250a9b2009-03-10 17:39:46 +0000421 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000422 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000423 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000424 'gif': 'image/gif',
425 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000426 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000427 'pdf' : 'application/pdf',
428 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000429 }
initial.commit94958cf2008-07-26 22:42:52 +0000430 self._default_mime_type = 'text/html'
431
akalin@chromium.org154bb132010-11-12 02:20:27 +0000432 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000433 connect_handlers, get_handlers, head_handlers,
434 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435
initial.commit94958cf2008-07-26 22:42:52 +0000436 def GetMIMETypeFromName(self, file_name):
437 """Returns the mime type for the specified file_name. So far it only looks
438 at the file extension."""
439
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000440 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000441 if len(extension) == 0:
442 # no extension.
443 return self._default_mime_type
444
ericroman@google.comc17ca532009-05-07 03:51:05 +0000445 # extension starts with a dot, so we need to remove it
446 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000447
initial.commit94958cf2008-07-26 22:42:52 +0000448 def NoCacheMaxAgeTimeHandler(self):
449 """This request handler yields a page with the title set to the current
450 system time, and no caching requested."""
451
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000453 return False
454
455 self.send_response(200)
456 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000457 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000458 self.end_headers()
459
maruel@google.come250a9b2009-03-10 17:39:46 +0000460 self.wfile.write('<html><head><title>%s</title></head></html>' %
461 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000462
463 return True
464
465 def NoCacheTimeHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and no caching requested."""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
473 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000474 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def CacheTimeHandler(self):
483 """This request handler yields a page with the title set to the current
484 system time, and allows caching for one minute."""
485
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000486 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000487 return False
488
489 self.send_response(200)
490 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000491 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000492 self.end_headers()
493
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 self.wfile.write('<html><head><title>%s</title></head></html>' %
495 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000496
497 return True
498
499 def CacheExpiresHandler(self):
500 """This request handler yields a page with the title set to the current
501 system time, and set the page to expire on 1 Jan 2099."""
502
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000503 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000504 return False
505
506 self.send_response(200)
507 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000508 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000509 self.end_headers()
510
maruel@google.come250a9b2009-03-10 17:39:46 +0000511 self.wfile.write('<html><head><title>%s</title></head></html>' %
512 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 return True
515
516 def CacheProxyRevalidateHandler(self):
517 """This request handler yields a page with the title set to the current
518 system time, and allows caching for 60 seconds"""
519
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000520 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000521 return False
522
523 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000524 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000525 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
526 self.end_headers()
527
maruel@google.come250a9b2009-03-10 17:39:46 +0000528 self.wfile.write('<html><head><title>%s</title></head></html>' %
529 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000530
531 return True
532
533 def CachePrivateHandler(self):
534 """This request handler yields a page with the title set to the current
535 system time, and allows caching for 5 seconds."""
536
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000537 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000538 return False
539
540 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000541 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000542 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000543 self.end_headers()
544
maruel@google.come250a9b2009-03-10 17:39:46 +0000545 self.wfile.write('<html><head><title>%s</title></head></html>' %
546 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000547
548 return True
549
550 def CachePublicHandler(self):
551 """This request handler yields a page with the title set to the current
552 system time, and allows caching for 5 seconds."""
553
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000554 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000555 return False
556
557 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000558 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000559 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000560 self.end_headers()
561
maruel@google.come250a9b2009-03-10 17:39:46 +0000562 self.wfile.write('<html><head><title>%s</title></head></html>' %
563 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000564
565 return True
566
567 def CacheSMaxAgeHandler(self):
568 """This request handler yields a page with the title set to the current
569 system time, and does not allow for caching."""
570
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000571 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000572 return False
573
574 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000575 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000576 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
577 self.end_headers()
578
maruel@google.come250a9b2009-03-10 17:39:46 +0000579 self.wfile.write('<html><head><title>%s</title></head></html>' %
580 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000581
582 return True
583
584 def CacheMustRevalidateHandler(self):
585 """This request handler yields a page with the title set to the current
586 system time, and does not allow caching."""
587
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000588 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000589 return False
590
591 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000592 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000593 self.send_header('Cache-Control', 'must-revalidate')
594 self.end_headers()
595
maruel@google.come250a9b2009-03-10 17:39:46 +0000596 self.wfile.write('<html><head><title>%s</title></head></html>' %
597 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000598
599 return True
600
601 def CacheMustRevalidateMaxAgeHandler(self):
602 """This request handler yields a page with the title set to the current
603 system time, and does not allow caching event though max-age of 60
604 seconds is specified."""
605
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000606 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000607 return False
608
609 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000610 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000611 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
612 self.end_headers()
613
maruel@google.come250a9b2009-03-10 17:39:46 +0000614 self.wfile.write('<html><head><title>%s</title></head></html>' %
615 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000616
617 return True
618
initial.commit94958cf2008-07-26 22:42:52 +0000619 def CacheNoStoreHandler(self):
620 """This request handler yields a page with the title set to the current
621 system time, and does not allow the page to be stored."""
622
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000623 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000624 return False
625
626 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000627 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000628 self.send_header('Cache-Control', 'no-store')
629 self.end_headers()
630
maruel@google.come250a9b2009-03-10 17:39:46 +0000631 self.wfile.write('<html><head><title>%s</title></head></html>' %
632 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000633
634 return True
635
636 def CacheNoStoreMaxAgeHandler(self):
637 """This request handler yields a page with the title set to the current
638 system time, and does not allow the page to be stored even though max-age
639 of 60 seconds is specified."""
640
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000641 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000642 return False
643
644 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000645 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000646 self.send_header('Cache-Control', 'max-age=60, no-store')
647 self.end_headers()
648
maruel@google.come250a9b2009-03-10 17:39:46 +0000649 self.wfile.write('<html><head><title>%s</title></head></html>' %
650 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000651
652 return True
653
654
655 def CacheNoTransformHandler(self):
656 """This request handler yields a page with the title set to the current
657 system time, and does not allow the content to transformed during
658 user-agent caching"""
659
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000660 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000661 return False
662
663 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000664 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000665 self.send_header('Cache-Control', 'no-transform')
666 self.end_headers()
667
maruel@google.come250a9b2009-03-10 17:39:46 +0000668 self.wfile.write('<html><head><title>%s</title></head></html>' %
669 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000670
671 return True
672
673 def EchoHeader(self):
674 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000675 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000676
ananta@chromium.org56812d02011-04-07 17:52:05 +0000677 """This function echoes back the value of a specific request header"""
678 """while allowing caching for 16 hours."""
679 def EchoHeaderCache(self):
680 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000681
682 def EchoHeaderHelper(self, echo_header):
683 """This function echoes back the value of the request header passed in."""
684 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000685 return False
686
687 query_char = self.path.find('?')
688 if query_char != -1:
689 header_name = self.path[query_char+1:]
690
691 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000692 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000693 if echo_header == '/echoheadercache':
694 self.send_header('Cache-control', 'max-age=60000')
695 else:
696 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000697 # insert a vary header to properly indicate that the cachability of this
698 # request is subject to value of the request header being echoed.
699 if len(header_name) > 0:
700 self.send_header('Vary', header_name)
701 self.end_headers()
702
703 if len(header_name) > 0:
704 self.wfile.write(self.headers.getheader(header_name))
705
706 return True
707
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000708 def ReadRequestBody(self):
709 """This function reads the body of the current HTTP request, handling
710 both plain and chunked transfer encoded requests."""
711
712 if self.headers.getheader('transfer-encoding') != 'chunked':
713 length = int(self.headers.getheader('content-length'))
714 return self.rfile.read(length)
715
716 # Read the request body as chunks.
717 body = ""
718 while True:
719 line = self.rfile.readline()
720 length = int(line, 16)
721 if length == 0:
722 self.rfile.readline()
723 break
724 body += self.rfile.read(length)
725 self.rfile.read(2)
726 return body
727
initial.commit94958cf2008-07-26 22:42:52 +0000728 def EchoHandler(self):
729 """This handler just echoes back the payload of the request, for testing
730 form submission."""
731
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000732 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000733 return False
734
735 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000736 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000737 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000738 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000739 return True
740
741 def EchoTitleHandler(self):
742 """This handler is like Echo, but sets the page title to the request."""
743
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000744 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000745 return False
746
747 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000748 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000749 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000750 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000751 self.wfile.write('<html><head><title>')
752 self.wfile.write(request)
753 self.wfile.write('</title></head></html>')
754 return True
755
756 def EchoAllHandler(self):
757 """This handler yields a (more) human-readable page listing information
758 about the request header & contents."""
759
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000760 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000761 return False
762
763 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000764 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000765 self.end_headers()
766 self.wfile.write('<html><head><style>'
767 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
768 '</style></head><body>'
769 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000770 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000771 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000772
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000773 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000774 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000775 params = cgi.parse_qs(qs, keep_blank_values=1)
776
777 for param in params:
778 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000779
780 self.wfile.write('</pre>')
781
782 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
783
784 self.wfile.write('</body></html>')
785 return True
786
787 def DownloadHandler(self):
788 """This handler sends a downloadable file with or without reporting
789 the size (6K)."""
790
791 if self.path.startswith("/download-unknown-size"):
792 send_length = False
793 elif self.path.startswith("/download-known-size"):
794 send_length = True
795 else:
796 return False
797
798 #
799 # The test which uses this functionality is attempting to send
800 # small chunks of data to the client. Use a fairly large buffer
801 # so that we'll fill chrome's IO buffer enough to force it to
802 # actually write the data.
803 # See also the comments in the client-side of this test in
804 # download_uitest.cc
805 #
806 size_chunk1 = 35*1024
807 size_chunk2 = 10*1024
808
809 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000810 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000811 self.send_header('Cache-Control', 'max-age=0')
812 if send_length:
813 self.send_header('Content-Length', size_chunk1 + size_chunk2)
814 self.end_headers()
815
816 # First chunk of data:
817 self.wfile.write("*" * size_chunk1)
818 self.wfile.flush()
819
820 # handle requests until one of them clears this flag.
821 self.server.waitForDownload = True
822 while self.server.waitForDownload:
823 self.server.handle_request()
824
825 # Second chunk of data:
826 self.wfile.write("*" * size_chunk2)
827 return True
828
829 def DownloadFinishHandler(self):
830 """This handler just tells the server to finish the current download."""
831
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000832 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000833 return False
834
835 self.server.waitForDownload = False
836 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000837 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000838 self.send_header('Cache-Control', 'max-age=0')
839 self.end_headers()
840 return True
841
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000842 def _ReplaceFileData(self, data, query_parameters):
843 """Replaces matching substrings in a file.
844
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000845 If the 'replace_text' URL query parameter is present, it is expected to be
846 of the form old_text:new_text, which indicates that any old_text strings in
847 the file are replaced with new_text. Multiple 'replace_text' parameters may
848 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000849
850 If the parameters are not present, |data| is returned.
851 """
852 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000853 replace_text_values = query_dict.get('replace_text', [])
854 for replace_text_value in replace_text_values:
855 replace_text_args = replace_text_value.split(':')
856 if len(replace_text_args) != 2:
857 raise ValueError(
858 'replace_text must be of form old_text:new_text. Actual value: %s' %
859 replace_text_value)
860 old_text_b64, new_text_b64 = replace_text_args
861 old_text = base64.urlsafe_b64decode(old_text_b64)
862 new_text = base64.urlsafe_b64decode(new_text_b64)
863 data = data.replace(old_text, new_text)
864 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000865
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000866 def ZipFileHandler(self):
867 """This handler sends the contents of the requested file in compressed form.
868 Can pass in a parameter that specifies that the content length be
869 C - the compressed size (OK),
870 U - the uncompressed size (Non-standard, but handled),
871 S - less than compressed (OK because we keep going),
872 M - larger than compressed but less than uncompressed (an error),
873 L - larger than uncompressed (an error)
874 Example: compressedfiles/Picture_1.doc?C
875 """
876
877 prefix = "/compressedfiles/"
878 if not self.path.startswith(prefix):
879 return False
880
881 # Consume a request body if present.
882 if self.command == 'POST' or self.command == 'PUT' :
883 self.ReadRequestBody()
884
885 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
886
887 if not query in ('C', 'U', 'S', 'M', 'L'):
888 return False
889
890 sub_path = url_path[len(prefix):]
891 entries = sub_path.split('/')
892 file_path = os.path.join(self.server.data_dir, *entries)
893 if os.path.isdir(file_path):
894 file_path = os.path.join(file_path, 'index.html')
895
896 if not os.path.isfile(file_path):
897 print "File not found " + sub_path + " full path:" + file_path
898 self.send_error(404)
899 return True
900
901 f = open(file_path, "rb")
902 data = f.read()
903 uncompressed_len = len(data)
904 f.close()
905
906 # Compress the data.
907 data = zlib.compress(data)
908 compressed_len = len(data)
909
910 content_length = compressed_len
911 if query == 'U':
912 content_length = uncompressed_len
913 elif query == 'S':
914 content_length = compressed_len / 2
915 elif query == 'M':
916 content_length = (compressed_len + uncompressed_len) / 2
917 elif query == 'L':
918 content_length = compressed_len + uncompressed_len
919
920 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000921 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000922 self.send_header('Content-encoding', 'deflate')
923 self.send_header('Connection', 'close')
924 self.send_header('Content-Length', content_length)
925 self.send_header('ETag', '\'' + file_path + '\'')
926 self.end_headers()
927
928 self.wfile.write(data)
929
930 return True
931
initial.commit94958cf2008-07-26 22:42:52 +0000932 def FileHandler(self):
933 """This handler sends the contents of the requested file. Wow, it's like
934 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000935 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000936 if not self.path.startswith(prefix):
937 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000938 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000939
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000940 def PostOnlyFileHandler(self):
941 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000942 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000943 if not self.path.startswith(prefix):
944 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000945 return self._FileHandlerHelper(prefix)
946
947 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000948 request_body = ''
949 if self.command == 'POST' or self.command == 'PUT':
950 # Consume a request body if present.
951 request_body = self.ReadRequestBody()
952
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000953 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000954 query_dict = cgi.parse_qs(query)
955
956 expected_body = query_dict.get('expected_body', [])
957 if expected_body and request_body not in expected_body:
958 self.send_response(404)
959 self.end_headers()
960 self.wfile.write('')
961 return True
962
963 expected_headers = query_dict.get('expected_headers', [])
964 for expected_header in expected_headers:
965 header_name, expected_value = expected_header.split(':')
966 if self.headers.getheader(header_name) != expected_value:
967 self.send_response(404)
968 self.end_headers()
969 self.wfile.write('')
970 return True
971
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000972 sub_path = url_path[len(prefix):]
973 entries = sub_path.split('/')
974 file_path = os.path.join(self.server.data_dir, *entries)
975 if os.path.isdir(file_path):
976 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000977
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000978 if not os.path.isfile(file_path):
979 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000980 self.send_error(404)
981 return True
982
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000983 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000984 data = f.read()
985 f.close()
986
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000987 data = self._ReplaceFileData(data, query)
988
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000989 old_protocol_version = self.protocol_version
990
initial.commit94958cf2008-07-26 22:42:52 +0000991 # If file.mock-http-headers exists, it contains the headers we
992 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000993 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000994 if os.path.isfile(headers_path):
995 f = open(headers_path, "r")
996
997 # "HTTP/1.1 200 OK"
998 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000999 http_major, http_minor, status_code = re.findall(
1000 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1001 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001002 self.send_response(int(status_code))
1003
1004 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001005 header_values = re.findall('(\S+):\s*(.*)', line)
1006 if len(header_values) > 0:
1007 # "name: value"
1008 name, value = header_values[0]
1009 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001010 f.close()
1011 else:
1012 # Could be more generic once we support mime-type sniffing, but for
1013 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001014
1015 range = self.headers.get('Range')
1016 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001017 # Note this doesn't handle all valid byte range values (i.e. left
1018 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001019 range = range[6:].split('-')
1020 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001021 if range[1]:
1022 end = int(range[1])
1023 else:
1024 end = len(data)
jam@chromium.org41550782010-11-17 23:47:50 +00001025
1026 self.send_response(206)
1027 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1028 str(len(data))
1029 self.send_header('Content-Range', content_range)
1030 data = data[start: end + 1]
1031 else:
1032 self.send_response(200)
1033
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001034 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001035 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001036 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001037 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001038 self.end_headers()
1039
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001040 if (self.command != 'HEAD'):
1041 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001042
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001043 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001044 return True
1045
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001046 def SetCookieHandler(self):
1047 """This handler just sets a cookie, for testing cookie handling."""
1048
1049 if not self._ShouldHandleRequest("/set-cookie"):
1050 return False
1051
1052 query_char = self.path.find('?')
1053 if query_char != -1:
1054 cookie_values = self.path[query_char + 1:].split('&')
1055 else:
1056 cookie_values = ("",)
1057 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001058 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001059 for cookie_value in cookie_values:
1060 self.send_header('Set-Cookie', '%s' % cookie_value)
1061 self.end_headers()
1062 for cookie_value in cookie_values:
1063 self.wfile.write('%s' % cookie_value)
1064 return True
1065
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001066 def SetHeaderHandler(self):
1067 """This handler sets a response header. Parameters are in the
1068 key%3A%20value&key2%3A%20value2 format."""
1069
1070 if not self._ShouldHandleRequest("/set-header"):
1071 return False
1072
1073 query_char = self.path.find('?')
1074 if query_char != -1:
1075 headers_values = self.path[query_char + 1:].split('&')
1076 else:
1077 headers_values = ("",)
1078 self.send_response(200)
1079 self.send_header('Content-Type', 'text/html')
1080 for header_value in headers_values:
1081 header_value = urllib.unquote(header_value)
1082 (key, value) = header_value.split(': ', 1)
1083 self.send_header(key, value)
1084 self.end_headers()
1085 for header_value in headers_values:
1086 self.wfile.write('%s' % header_value)
1087 return True
1088
initial.commit94958cf2008-07-26 22:42:52 +00001089 def AuthBasicHandler(self):
1090 """This handler tests 'Basic' authentication. It just sends a page with
1091 title 'user/pass' if you succeed."""
1092
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001093 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001094 return False
1095
1096 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001097 expected_password = 'secret'
1098 realm = 'testrealm'
1099 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001100
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001101 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1102 query_params = cgi.parse_qs(query, True)
1103 if 'set-cookie-if-challenged' in query_params:
1104 set_cookie_if_challenged = True
1105 if 'password' in query_params:
1106 expected_password = query_params['password'][0]
1107 if 'realm' in query_params:
1108 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001109
initial.commit94958cf2008-07-26 22:42:52 +00001110 auth = self.headers.getheader('authorization')
1111 try:
1112 if not auth:
1113 raise Exception('no auth')
1114 b64str = re.findall(r'Basic (\S+)', auth)[0]
1115 userpass = base64.b64decode(b64str)
1116 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001117 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001118 raise Exception('wrong password')
1119 except Exception, e:
1120 # Authentication failed.
1121 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001122 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001123 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001124 if set_cookie_if_challenged:
1125 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001126 self.end_headers()
1127 self.wfile.write('<html><head>')
1128 self.wfile.write('<title>Denied: %s</title>' % e)
1129 self.wfile.write('</head><body>')
1130 self.wfile.write('auth=%s<p>' % auth)
1131 self.wfile.write('b64str=%s<p>' % b64str)
1132 self.wfile.write('username: %s<p>' % username)
1133 self.wfile.write('userpass: %s<p>' % userpass)
1134 self.wfile.write('password: %s<p>' % password)
1135 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1136 self.wfile.write('</body></html>')
1137 return True
1138
1139 # Authentication successful. (Return a cachable response to allow for
1140 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001141 old_protocol_version = self.protocol_version
1142 self.protocol_version = "HTTP/1.1"
1143
initial.commit94958cf2008-07-26 22:42:52 +00001144 if_none_match = self.headers.getheader('if-none-match')
1145 if if_none_match == "abc":
1146 self.send_response(304)
1147 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001148 elif url_path.endswith(".gif"):
1149 # Using chrome/test/data/google/logo.gif as the test image
1150 test_image_path = ['google', 'logo.gif']
1151 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1152 if not os.path.isfile(gif_path):
1153 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001154 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001155 return True
1156
1157 f = open(gif_path, "rb")
1158 data = f.read()
1159 f.close()
1160
1161 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001162 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001163 self.send_header('Cache-control', 'max-age=60000')
1164 self.send_header('Etag', 'abc')
1165 self.end_headers()
1166 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001167 else:
1168 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001169 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001170 self.send_header('Cache-control', 'max-age=60000')
1171 self.send_header('Etag', 'abc')
1172 self.end_headers()
1173 self.wfile.write('<html><head>')
1174 self.wfile.write('<title>%s/%s</title>' % (username, password))
1175 self.wfile.write('</head><body>')
1176 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001177 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001178 self.wfile.write('</body></html>')
1179
rvargas@google.com54453b72011-05-19 01:11:11 +00001180 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001181 return True
1182
tonyg@chromium.org75054202010-03-31 22:06:10 +00001183 def GetNonce(self, force_reset=False):
1184 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001185
tonyg@chromium.org75054202010-03-31 22:06:10 +00001186 This is a fake implementation. A real implementation would only use a given
1187 nonce a single time (hence the name n-once). However, for the purposes of
1188 unittesting, we don't care about the security of the nonce.
1189
1190 Args:
1191 force_reset: Iff set, the nonce will be changed. Useful for testing the
1192 "stale" response.
1193 """
1194 if force_reset or not self.server.nonce_time:
1195 self.server.nonce_time = time.time()
1196 return _new_md5('privatekey%s%d' %
1197 (self.path, self.server.nonce_time)).hexdigest()
1198
1199 def AuthDigestHandler(self):
1200 """This handler tests 'Digest' authentication.
1201
1202 It just sends a page with title 'user/pass' if you succeed.
1203
1204 A stale response is sent iff "stale" is present in the request path.
1205 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001206 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001207 return False
1208
tonyg@chromium.org75054202010-03-31 22:06:10 +00001209 stale = 'stale' in self.path
1210 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001211 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001212 password = 'secret'
1213 realm = 'testrealm'
1214
1215 auth = self.headers.getheader('authorization')
1216 pairs = {}
1217 try:
1218 if not auth:
1219 raise Exception('no auth')
1220 if not auth.startswith('Digest'):
1221 raise Exception('not digest')
1222 # Pull out all the name="value" pairs as a dictionary.
1223 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1224
1225 # Make sure it's all valid.
1226 if pairs['nonce'] != nonce:
1227 raise Exception('wrong nonce')
1228 if pairs['opaque'] != opaque:
1229 raise Exception('wrong opaque')
1230
1231 # Check the 'response' value and make sure it matches our magic hash.
1232 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001233 hash_a1 = _new_md5(
1234 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001235 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001236 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001237 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001238 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1239 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001240 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001241
1242 if pairs['response'] != response:
1243 raise Exception('wrong password')
1244 except Exception, e:
1245 # Authentication failed.
1246 self.send_response(401)
1247 hdr = ('Digest '
1248 'realm="%s", '
1249 'domain="/", '
1250 'qop="auth", '
1251 'algorithm=MD5, '
1252 'nonce="%s", '
1253 'opaque="%s"') % (realm, nonce, opaque)
1254 if stale:
1255 hdr += ', stale="TRUE"'
1256 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001257 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001258 self.end_headers()
1259 self.wfile.write('<html><head>')
1260 self.wfile.write('<title>Denied: %s</title>' % e)
1261 self.wfile.write('</head><body>')
1262 self.wfile.write('auth=%s<p>' % auth)
1263 self.wfile.write('pairs=%s<p>' % pairs)
1264 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1265 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1266 self.wfile.write('</body></html>')
1267 return True
1268
1269 # Authentication successful.
1270 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001271 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001272 self.end_headers()
1273 self.wfile.write('<html><head>')
1274 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1275 self.wfile.write('</head><body>')
1276 self.wfile.write('auth=%s<p>' % auth)
1277 self.wfile.write('pairs=%s<p>' % pairs)
1278 self.wfile.write('</body></html>')
1279
1280 return True
1281
1282 def SlowServerHandler(self):
1283 """Wait for the user suggested time before responding. The syntax is
1284 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001285 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001286 return False
1287 query_char = self.path.find('?')
1288 wait_sec = 1.0
1289 if query_char >= 0:
1290 try:
1291 wait_sec = int(self.path[query_char + 1:])
1292 except ValueError:
1293 pass
1294 time.sleep(wait_sec)
1295 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001296 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001297 self.end_headers()
1298 self.wfile.write("waited %d seconds" % wait_sec)
1299 return True
1300
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001301 def ChunkedServerHandler(self):
1302 """Send chunked response. Allows to specify chunks parameters:
1303 - waitBeforeHeaders - ms to wait before sending headers
1304 - waitBetweenChunks - ms to wait between chunks
1305 - chunkSize - size of each chunk in bytes
1306 - chunksNumber - number of chunks
1307 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1308 waits one second, then sends headers and five chunks five bytes each."""
1309 if not self._ShouldHandleRequest("/chunked"):
1310 return False
1311 query_char = self.path.find('?')
1312 chunkedSettings = {'waitBeforeHeaders' : 0,
1313 'waitBetweenChunks' : 0,
1314 'chunkSize' : 5,
1315 'chunksNumber' : 5}
1316 if query_char >= 0:
1317 params = self.path[query_char + 1:].split('&')
1318 for param in params:
1319 keyValue = param.split('=')
1320 if len(keyValue) == 2:
1321 try:
1322 chunkedSettings[keyValue[0]] = int(keyValue[1])
1323 except ValueError:
1324 pass
1325 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1326 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1327 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001328 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001329 self.send_header('Connection', 'close')
1330 self.send_header('Transfer-Encoding', 'chunked')
1331 self.end_headers()
1332 # Chunked encoding: sending all chunks, then final zero-length chunk and
1333 # then final CRLF.
1334 for i in range(0, chunkedSettings['chunksNumber']):
1335 if i > 0:
1336 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1337 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1338 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1339 self.sendChunkHelp('')
1340 return True
1341
initial.commit94958cf2008-07-26 22:42:52 +00001342 def ContentTypeHandler(self):
1343 """Returns a string of html with the given content type. E.g.,
1344 /contenttype?text/css returns an html file with the Content-Type
1345 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001346 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001347 return False
1348 query_char = self.path.find('?')
1349 content_type = self.path[query_char + 1:].strip()
1350 if not content_type:
1351 content_type = 'text/html'
1352 self.send_response(200)
1353 self.send_header('Content-Type', content_type)
1354 self.end_headers()
1355 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1356 return True
1357
creis@google.com2f4f6a42011-03-25 19:44:19 +00001358 def NoContentHandler(self):
1359 """Returns a 204 No Content response."""
1360 if not self._ShouldHandleRequest("/nocontent"):
1361 return False
1362 self.send_response(204)
1363 self.end_headers()
1364 return True
1365
initial.commit94958cf2008-07-26 22:42:52 +00001366 def ServerRedirectHandler(self):
1367 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001368 '/server-redirect?http://foo.bar/asdf' to redirect to
1369 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001370
1371 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001372 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001373 return False
1374
1375 query_char = self.path.find('?')
1376 if query_char < 0 or len(self.path) <= query_char + 1:
1377 self.sendRedirectHelp(test_name)
1378 return True
1379 dest = self.path[query_char + 1:]
1380
1381 self.send_response(301) # moved permanently
1382 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001383 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001384 self.end_headers()
1385 self.wfile.write('<html><head>')
1386 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1387
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001388 return True
initial.commit94958cf2008-07-26 22:42:52 +00001389
1390 def ClientRedirectHandler(self):
1391 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001392 '/client-redirect?http://foo.bar/asdf' to redirect to
1393 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001394
1395 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001396 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001397 return False
1398
1399 query_char = self.path.find('?');
1400 if query_char < 0 or len(self.path) <= query_char + 1:
1401 self.sendRedirectHelp(test_name)
1402 return True
1403 dest = self.path[query_char + 1:]
1404
1405 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001406 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001407 self.end_headers()
1408 self.wfile.write('<html><head>')
1409 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1410 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1411
1412 return True
1413
tony@chromium.org03266982010-03-05 03:18:42 +00001414 def MultipartHandler(self):
1415 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001416 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001417 if not self._ShouldHandleRequest(test_name):
1418 return False
1419
1420 num_frames = 10
1421 bound = '12345'
1422 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001423 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001424 'multipart/x-mixed-replace;boundary=' + bound)
1425 self.end_headers()
1426
1427 for i in xrange(num_frames):
1428 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001429 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001430 self.wfile.write('<title>page ' + str(i) + '</title>')
1431 self.wfile.write('page ' + str(i))
1432
1433 self.wfile.write('--' + bound + '--')
1434 return True
1435
tony@chromium.org4cb88302011-09-27 22:13:49 +00001436 def MultipartSlowHandler(self):
1437 """Send a multipart response (3 text/html pages) with a slight delay
1438 between each page. This is similar to how some pages show status using
1439 multipart."""
1440 test_name = '/multipart-slow'
1441 if not self._ShouldHandleRequest(test_name):
1442 return False
1443
1444 num_frames = 3
1445 bound = '12345'
1446 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001447 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001448 'multipart/x-mixed-replace;boundary=' + bound)
1449 self.end_headers()
1450
1451 for i in xrange(num_frames):
1452 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001453 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001454 time.sleep(0.25)
1455 if i == 2:
1456 self.wfile.write('<title>PASS</title>')
1457 else:
1458 self.wfile.write('<title>page ' + str(i) + '</title>')
1459 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1460
1461 self.wfile.write('--' + bound + '--')
1462 return True
1463
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001464 def GetSSLSessionCacheHandler(self):
1465 """Send a reply containing a log of the session cache operations."""
1466
1467 if not self._ShouldHandleRequest('/ssl-session-cache'):
1468 return False
1469
1470 self.send_response(200)
1471 self.send_header('Content-Type', 'text/plain')
1472 self.end_headers()
1473 try:
1474 for (action, sessionID) in self.server.session_cache.log:
1475 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1476 except AttributeError, e:
1477 self.wfile.write('Pass --https-record-resume in order to use' +
1478 ' this request')
1479 return True
1480
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001481 def CloseSocketHandler(self):
1482 """Closes the socket without sending anything."""
1483
1484 if not self._ShouldHandleRequest('/close-socket'):
1485 return False
1486
1487 self.wfile.close()
1488 return True
1489
initial.commit94958cf2008-07-26 22:42:52 +00001490 def DefaultResponseHandler(self):
1491 """This is the catch-all response handler for requests that aren't handled
1492 by one of the special handlers above.
1493 Note that we specify the content-length as without it the https connection
1494 is not closed properly (and the browser keeps expecting data)."""
1495
1496 contents = "Default response given for path: " + self.path
1497 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001498 self.send_header('Content-Type', 'text/html')
1499 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001500 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001501 if (self.command != 'HEAD'):
1502 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001503 return True
1504
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001505 def RedirectConnectHandler(self):
1506 """Sends a redirect to the CONNECT request for www.redirect.com. This
1507 response is not specified by the RFC, so the browser should not follow
1508 the redirect."""
1509
1510 if (self.path.find("www.redirect.com") < 0):
1511 return False
1512
1513 dest = "http://www.destination.com/foo.js"
1514
1515 self.send_response(302) # moved temporarily
1516 self.send_header('Location', dest)
1517 self.send_header('Connection', 'close')
1518 self.end_headers()
1519 return True
1520
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001521 def ServerAuthConnectHandler(self):
1522 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1523 response doesn't make sense because the proxy server cannot request
1524 server authentication."""
1525
1526 if (self.path.find("www.server-auth.com") < 0):
1527 return False
1528
1529 challenge = 'Basic realm="WallyWorld"'
1530
1531 self.send_response(401) # unauthorized
1532 self.send_header('WWW-Authenticate', challenge)
1533 self.send_header('Connection', 'close')
1534 self.end_headers()
1535 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001536
1537 def DefaultConnectResponseHandler(self):
1538 """This is the catch-all response handler for CONNECT requests that aren't
1539 handled by one of the special handlers above. Real Web servers respond
1540 with 400 to CONNECT requests."""
1541
1542 contents = "Your client has issued a malformed or illegal request."
1543 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001544 self.send_header('Content-Type', 'text/html')
1545 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001546 self.end_headers()
1547 self.wfile.write(contents)
1548 return True
1549
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001550 def DeviceManagementHandler(self):
1551 """Delegates to the device management service used for cloud policy."""
1552 if not self._ShouldHandleRequest("/device_management"):
1553 return False
1554
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001555 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001556
1557 if not self.server._device_management_handler:
1558 import device_management
1559 policy_path = os.path.join(self.server.data_dir, 'device_management')
1560 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001561 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001562 self.server.policy_keys,
1563 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001564
1565 http_response, raw_reply = (
1566 self.server._device_management_handler.HandleRequest(self.path,
1567 self.headers,
1568 raw_request))
1569 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001570 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001571 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001572 self.end_headers()
1573 self.wfile.write(raw_reply)
1574 return True
1575
initial.commit94958cf2008-07-26 22:42:52 +00001576 # called by the redirect handling function when there is no parameter
1577 def sendRedirectHelp(self, redirect_name):
1578 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001579 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001580 self.end_headers()
1581 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1582 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1583 self.wfile.write('</body></html>')
1584
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001585 # called by chunked handling function
1586 def sendChunkHelp(self, chunk):
1587 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1588 self.wfile.write('%X\r\n' % len(chunk))
1589 self.wfile.write(chunk)
1590 self.wfile.write('\r\n')
1591
akalin@chromium.org154bb132010-11-12 02:20:27 +00001592
1593class SyncPageHandler(BasePageHandler):
1594 """Handler for the main HTTP sync server."""
1595
1596 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001597 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001598 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001599 self.ChromiumSyncDisableNotificationsOpHandler,
1600 self.ChromiumSyncEnableNotificationsOpHandler,
1601 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001602 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001603 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001604 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001605 self.ChromiumSyncErrorOpHandler,
1606 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001607
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001608 post_handlers = [self.ChromiumSyncCommandHandler,
1609 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001610 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001611 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001612 post_handlers, [])
1613
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001614
akalin@chromium.org154bb132010-11-12 02:20:27 +00001615 def ChromiumSyncTimeHandler(self):
1616 """Handle Chromium sync .../time requests.
1617
1618 The syncer sometimes checks server reachability by examining /time.
1619 """
1620 test_name = "/chromiumsync/time"
1621 if not self._ShouldHandleRequest(test_name):
1622 return False
1623
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001624 # Chrome hates it if we send a response before reading the request.
1625 if self.headers.getheader('content-length'):
1626 length = int(self.headers.getheader('content-length'))
1627 raw_request = self.rfile.read(length)
1628
akalin@chromium.org154bb132010-11-12 02:20:27 +00001629 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001630 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001631 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001632 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001633 return True
1634
1635 def ChromiumSyncCommandHandler(self):
1636 """Handle a chromiumsync command arriving via http.
1637
1638 This covers all sync protocol commands: authentication, getupdates, and
1639 commit.
1640 """
1641 test_name = "/chromiumsync/command"
1642 if not self._ShouldHandleRequest(test_name):
1643 return False
1644
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001645 length = int(self.headers.getheader('content-length'))
1646 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001647 http_response = 200
1648 raw_reply = None
1649 if not self.server.GetAuthenticated():
1650 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001651 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1652 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001653 else:
1654 http_response, raw_reply = self.server.HandleCommand(
1655 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001656
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001657 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001658 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001659 if http_response == 401:
1660 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001661 self.end_headers()
1662 self.wfile.write(raw_reply)
1663 return True
1664
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001665 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001666 test_name = "/chromiumsync/migrate"
1667 if not self._ShouldHandleRequest(test_name):
1668 return False
1669
1670 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1671 self.path)
1672 self.send_response(http_response)
1673 self.send_header('Content-Type', 'text/html')
1674 self.send_header('Content-Length', len(raw_reply))
1675 self.end_headers()
1676 self.wfile.write(raw_reply)
1677 return True
1678
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001679 def ChromiumSyncCredHandler(self):
1680 test_name = "/chromiumsync/cred"
1681 if not self._ShouldHandleRequest(test_name):
1682 return False
1683 try:
1684 query = urlparse.urlparse(self.path)[4]
1685 cred_valid = urlparse.parse_qs(query)['valid']
1686 if cred_valid[0] == 'True':
1687 self.server.SetAuthenticated(True)
1688 else:
1689 self.server.SetAuthenticated(False)
1690 except:
1691 self.server.SetAuthenticated(False)
1692
1693 http_response = 200
1694 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1695 self.send_response(http_response)
1696 self.send_header('Content-Type', 'text/html')
1697 self.send_header('Content-Length', len(raw_reply))
1698 self.end_headers()
1699 self.wfile.write(raw_reply)
1700 return True
1701
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001702 def ChromiumSyncDisableNotificationsOpHandler(self):
1703 test_name = "/chromiumsync/disablenotifications"
1704 if not self._ShouldHandleRequest(test_name):
1705 return False
1706 self.server.GetXmppServer().DisableNotifications()
1707 result = 200
1708 raw_reply = ('<html><title>Notifications disabled</title>'
1709 '<H1>Notifications disabled</H1></html>')
1710 self.send_response(result)
1711 self.send_header('Content-Type', 'text/html')
1712 self.send_header('Content-Length', len(raw_reply))
1713 self.end_headers()
1714 self.wfile.write(raw_reply)
1715 return True;
1716
1717 def ChromiumSyncEnableNotificationsOpHandler(self):
1718 test_name = "/chromiumsync/enablenotifications"
1719 if not self._ShouldHandleRequest(test_name):
1720 return False
1721 self.server.GetXmppServer().EnableNotifications()
1722 result = 200
1723 raw_reply = ('<html><title>Notifications enabled</title>'
1724 '<H1>Notifications enabled</H1></html>')
1725 self.send_response(result)
1726 self.send_header('Content-Type', 'text/html')
1727 self.send_header('Content-Length', len(raw_reply))
1728 self.end_headers()
1729 self.wfile.write(raw_reply)
1730 return True;
1731
1732 def ChromiumSyncSendNotificationOpHandler(self):
1733 test_name = "/chromiumsync/sendnotification"
1734 if not self._ShouldHandleRequest(test_name):
1735 return False
1736 query = urlparse.urlparse(self.path)[4]
1737 query_params = urlparse.parse_qs(query)
1738 channel = ''
1739 data = ''
1740 if 'channel' in query_params:
1741 channel = query_params['channel'][0]
1742 if 'data' in query_params:
1743 data = query_params['data'][0]
1744 self.server.GetXmppServer().SendNotification(channel, data)
1745 result = 200
1746 raw_reply = ('<html><title>Notification sent</title>'
1747 '<H1>Notification sent with channel "%s" '
1748 'and data "%s"</H1></html>'
1749 % (channel, data))
1750 self.send_response(result)
1751 self.send_header('Content-Type', 'text/html')
1752 self.send_header('Content-Length', len(raw_reply))
1753 self.end_headers()
1754 self.wfile.write(raw_reply)
1755 return True;
1756
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001757 def ChromiumSyncBirthdayErrorOpHandler(self):
1758 test_name = "/chromiumsync/birthdayerror"
1759 if not self._ShouldHandleRequest(test_name):
1760 return False
1761 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1762 self.send_response(result)
1763 self.send_header('Content-Type', 'text/html')
1764 self.send_header('Content-Length', len(raw_reply))
1765 self.end_headers()
1766 self.wfile.write(raw_reply)
1767 return True;
1768
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001769 def ChromiumSyncTransientErrorOpHandler(self):
1770 test_name = "/chromiumsync/transienterror"
1771 if not self._ShouldHandleRequest(test_name):
1772 return False
1773 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1774 self.send_response(result)
1775 self.send_header('Content-Type', 'text/html')
1776 self.send_header('Content-Length', len(raw_reply))
1777 self.end_headers()
1778 self.wfile.write(raw_reply)
1779 return True;
1780
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001781 def ChromiumSyncErrorOpHandler(self):
1782 test_name = "/chromiumsync/error"
1783 if not self._ShouldHandleRequest(test_name):
1784 return False
1785 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1786 self.path)
1787 self.send_response(result)
1788 self.send_header('Content-Type', 'text/html')
1789 self.send_header('Content-Length', len(raw_reply))
1790 self.end_headers()
1791 self.wfile.write(raw_reply)
1792 return True;
1793
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001794 def ChromiumSyncSyncTabsOpHandler(self):
1795 test_name = "/chromiumsync/synctabs"
1796 if not self._ShouldHandleRequest(test_name):
1797 return False
1798 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1799 self.send_response(result)
1800 self.send_header('Content-Type', 'text/html')
1801 self.send_header('Content-Length', len(raw_reply))
1802 self.end_headers()
1803 self.wfile.write(raw_reply)
1804 return True;
1805
akalin@chromium.org154bb132010-11-12 02:20:27 +00001806
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001807def MakeDataDir():
1808 if options.data_dir:
1809 if not os.path.isdir(options.data_dir):
1810 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1811 return None
1812 my_data_dir = options.data_dir
1813 else:
1814 # Create the default path to our data dir, relative to the exe dir.
1815 my_data_dir = os.path.dirname(sys.argv[0])
1816 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001817 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001818
1819 #TODO(ibrar): Must use Find* funtion defined in google\tools
1820 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1821
1822 return my_data_dir
1823
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001824
1825class TCPEchoHandler(SocketServer.BaseRequestHandler):
1826 """The RequestHandler class for TCP echo server.
1827
1828 It is instantiated once per connection to the server, and overrides the
1829 handle() method to implement communication to the client.
1830 """
1831
1832 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001833 """Handles the request from the client and constructs a response."""
1834
1835 data = self.request.recv(65536).strip()
1836 # Verify the "echo request" message received from the client. Send back
1837 # "echo response" message if "echo request" message is valid.
1838 try:
1839 return_data = echo_message.GetEchoResponseData(data)
1840 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001841 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001842 except ValueError:
1843 return
1844
1845 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001846
1847
1848class UDPEchoHandler(SocketServer.BaseRequestHandler):
1849 """The RequestHandler class for UDP echo server.
1850
1851 It is instantiated once per connection to the server, and overrides the
1852 handle() method to implement communication to the client.
1853 """
1854
1855 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001856 """Handles the request from the client and constructs a response."""
1857
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001858 data = self.request[0].strip()
1859 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001860 # Verify the "echo request" message received from the client. Send back
1861 # "echo response" message if "echo request" message is valid.
1862 try:
1863 return_data = echo_message.GetEchoResponseData(data)
1864 if not return_data:
1865 return
1866 except ValueError:
1867 return
1868 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001869
1870
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001871class FileMultiplexer:
1872 def __init__(self, fd1, fd2) :
1873 self.__fd1 = fd1
1874 self.__fd2 = fd2
1875
1876 def __del__(self) :
1877 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1878 self.__fd1.close()
1879 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1880 self.__fd2.close()
1881
1882 def write(self, text) :
1883 self.__fd1.write(text)
1884 self.__fd2.write(text)
1885
1886 def flush(self) :
1887 self.__fd1.flush()
1888 self.__fd2.flush()
1889
initial.commit94958cf2008-07-26 22:42:52 +00001890def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001891 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001892 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001893 if options.log_to_console:
1894 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1895 else:
1896 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001897
1898 port = options.port
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001899 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00001900
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001901 server_data = {}
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001902 server_data['host'] = host
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001903
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001904 if options.server_type == SERVER_HTTP:
1905 if options.cert:
1906 # let's make sure the cert file exists.
1907 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001908 print 'specified server cert file not found: ' + options.cert + \
1909 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001910 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001911 for ca_cert in options.ssl_client_ca:
1912 if not os.path.isfile(ca_cert):
1913 print 'specified trusted client CA file not found: ' + ca_cert + \
1914 ' exiting...'
1915 return
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001916 server = HTTPSServer((host, port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001917 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001918 options.ssl_bulk_cipher, options.record_resume)
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001919 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001920 else:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001921 server = HTTPServer((host, port), TestPageHandler)
1922 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00001923
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001924 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001925 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001926 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001927 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001928 server.policy_keys = options.policy_keys
1929 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001930 elif options.server_type == SERVER_SYNC:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001931 server = SyncHTTPServer((host, port), SyncPageHandler)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001932 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001933 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1934 server_data['port'] = server.server_port
1935 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001936 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001937 # Used for generating the key (randomly) that encodes the "echo request"
1938 # message.
1939 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001940 server = TCPEchoServer((host, port), TCPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001941 print 'Echo TCP server started on port %d...' % server.server_port
1942 server_data['port'] = server.server_port
1943 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001944 # Used for generating the key (randomly) that encodes the "echo request"
1945 # message.
1946 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001947 server = UDPEchoServer((host, port), UDPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001948 print 'Echo UDP server started on port %d...' % server.server_port
1949 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001950 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001951 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001952 my_data_dir = MakeDataDir()
1953
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001954 # Instantiate a dummy authorizer for managing 'virtual' users
1955 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1956
1957 # Define a new user having full r/w permissions and a read-only
1958 # anonymous user
1959 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1960
1961 authorizer.add_anonymous(my_data_dir)
1962
1963 # Instantiate FTP handler class
1964 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1965 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001966
1967 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001968 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1969 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001970
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001971 # Instantiate FTP server class and listen to address:port
1972 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001973 server_data['port'] = server.socket.getsockname()[1]
1974 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001975
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001976 # Notify the parent that we've started. (BaseServer subclasses
1977 # bind their sockets on construction.)
1978 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001979 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001980 server_data_len = len(server_data_json)
1981 print 'sending server_data: %s (%d bytes)' % (
1982 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001983 if sys.platform == 'win32':
1984 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1985 else:
1986 fd = options.startup_pipe
1987 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001988 # First write the data length as an unsigned 4-byte value. This
1989 # is _not_ using network byte ordering since the other end of the
1990 # pipe is on the same machine.
1991 startup_pipe.write(struct.pack('=L', server_data_len))
1992 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001993 startup_pipe.close()
1994
initial.commit94958cf2008-07-26 22:42:52 +00001995 try:
1996 server.serve_forever()
1997 except KeyboardInterrupt:
1998 print 'shutting down server'
1999 server.stop = True
2000
2001if __name__ == '__main__':
2002 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002003 option_parser.add_option("-f", '--ftp', action='store_const',
2004 const=SERVER_FTP, default=SERVER_HTTP,
2005 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00002006 help='start up an FTP server.')
2007 option_parser.add_option('', '--sync', action='store_const',
2008 const=SERVER_SYNC, default=SERVER_HTTP,
2009 dest='server_type',
2010 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002011 option_parser.add_option('', '--tcp-echo', action='store_const',
2012 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2013 dest='server_type',
2014 help='start up a tcp echo server.')
2015 option_parser.add_option('', '--udp-echo', action='store_const',
2016 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2017 dest='server_type',
2018 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002019 option_parser.add_option('', '--log-to-console', action='store_const',
2020 const=True, default=False,
2021 dest='log_to_console',
2022 help='Enables or disables sys.stdout logging to '
2023 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00002024 option_parser.add_option('', '--port', default='0', type='int',
2025 help='Port used by the server. If unspecified, the '
2026 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002027 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002028 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002029 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00002030 help='Specify that https should be used, specify '
2031 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002032 'the server should use.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002033 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2034 const=True, default=False, action='store_const',
2035 help='Record resumption cache events rather than'
2036 ' resuming as normal. Allows the use of the'
2037 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002038 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2039 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002040 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2041 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002042 'should include the CA named in the subject of '
2043 'the DER-encoded certificate contained in the '
2044 'specified file. This option may appear multiple '
2045 'times, indicating multiple CA names should be '
2046 'sent in the request.')
2047 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2048 help='Specify the bulk encryption algorithm(s)'
2049 'that will be accepted by the SSL server. Valid '
2050 'values are "aes256", "aes128", "3des", "rc4". If '
2051 'omitted, all algorithms will be used. This '
2052 'option may appear multiple times, indicating '
2053 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002054 option_parser.add_option('', '--file-root-url', default='/files/',
2055 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002056 option_parser.add_option('', '--startup-pipe', type='int',
2057 dest='startup_pipe',
2058 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002059 option_parser.add_option('', '--policy-key', action='append',
2060 dest='policy_keys',
2061 help='Specify a path to a PEM-encoded private key '
2062 'to use for policy signing. May be specified '
2063 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002064 'the server. If ther server has multiple keys, it '
2065 'will rotate through them in at each request a '
2066 'round-robin fashion. The server will generate a '
2067 'random key if none is specified on the command '
2068 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002069 option_parser.add_option('', '--policy-user', default='user@example.com',
2070 dest='policy_user',
2071 help='Specify the user name the server should '
2072 'report back to the client as the user owning the '
2073 'token used for making the policy request.')
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002074 option_parser.add_option('', '--host', default='127.0.0.1',
2075 dest='host',
2076 help='Hostname or IP upon which the server will '
2077 'listen. Client connections will also only be '
2078 'allowed from this address.')
initial.commit94958cf2008-07-26 22:42:52 +00002079 options, args = option_parser.parse_args()
2080
2081 sys.exit(main(options, args))