blob: cc66ffd3aee02960f98bab897898022c4e5e5924 [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00002# Copyright (c) 2011 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
initial.commit94958cf2008-07-26 22:42:52 +000021import optparse
22import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000023import random
initial.commit94958cf2008-07-26 22:42:52 +000024import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000025import select
akalin@chromium.org18e34882010-11-26 07:10:41 +000026import simplejson
initial.commit94958cf2008-07-26 22:42:52 +000027import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000028import socket
initial.commit94958cf2008-07-26 22:42:52 +000029import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000030import struct
initial.commit94958cf2008-07-26 22:42:52 +000031import time
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
davidben@chromium.org06fcf202010-09-22 18:15:23 +000051if sys.platform == 'win32':
52 import msvcrt
53
maruel@chromium.org756cf982009-03-05 12:46:38 +000054SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000055SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000056SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000057SERVER_TCP_ECHO = 3
58SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000059
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000060# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000061debug_output = sys.stderr
62def debug(str):
63 debug_output.write(str + "\n")
64 debug_output.flush()
65
66class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
67 """This is a specialization of of BaseHTTPServer to allow it
68 to be exited cleanly (by setting its "stop" member to True)."""
69
70 def serve_forever(self):
71 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000072 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000073 while not self.stop:
74 self.handle_request()
75 self.socket.close()
76
77class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
78 """This is a specialization of StoppableHTTPerver that add https support."""
79
davidben@chromium.org31282a12010-08-07 01:10:02 +000080 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000081 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000082 s = open(cert_path).read()
83 x509 = tlslite.api.X509()
84 x509.parse(s)
85 self.cert_chain = tlslite.api.X509CertChain([x509])
86 s = open(cert_path).read()
87 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000088 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000089 self.ssl_client_cas = []
90 for ca_file in ssl_client_cas:
91 s = open(ca_file).read()
92 x509 = tlslite.api.X509()
93 x509.parse(s)
94 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000095 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
96 if ssl_bulk_ciphers is not None:
97 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +000098
99 self.session_cache = tlslite.api.SessionCache()
100 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
101
102 def handshake(self, tlsConnection):
103 """Creates the SSL connection."""
104 try:
105 tlsConnection.handshakeServer(certChain=self.cert_chain,
106 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000107 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000108 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000109 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000110 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000111 tlsConnection.ignoreAbruptClose = True
112 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000113 except tlslite.api.TLSAbruptCloseError:
114 # Ignore abrupt close.
115 return True
initial.commit94958cf2008-07-26 22:42:52 +0000116 except tlslite.api.TLSError, error:
117 print "Handshake failure:", str(error)
118 return False
119
akalin@chromium.org154bb132010-11-12 02:20:27 +0000120
121class SyncHTTPServer(StoppableHTTPServer):
122 """An HTTP server that handles sync commands."""
123
124 def __init__(self, server_address, request_handler_class):
125 # We import here to avoid pulling in chromiumsync's dependencies
126 # unless strictly necessary.
127 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000128 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000129 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000130 self._sync_handler = chromiumsync.TestServer()
131 self._xmpp_socket_map = {}
132 self._xmpp_server = xmppserver.XmppServer(
133 self._xmpp_socket_map, ('localhost', 0))
134 self.xmpp_port = self._xmpp_server.getsockname()[1]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000135
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000136 def GetXmppServer(self):
137 return self._xmpp_server
138
akalin@chromium.org154bb132010-11-12 02:20:27 +0000139 def HandleCommand(self, query, raw_request):
140 return self._sync_handler.HandleCommand(query, raw_request)
141
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000142 def HandleRequestNoBlock(self):
143 """Handles a single request.
144
145 Copied from SocketServer._handle_request_noblock().
146 """
147 try:
148 request, client_address = self.get_request()
149 except socket.error:
150 return
151 if self.verify_request(request, client_address):
152 try:
153 self.process_request(request, client_address)
154 except:
155 self.handle_error(request, client_address)
156 self.close_request(request)
157
158 def serve_forever(self):
159 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
160 """
161
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000162 def HandleXmppSocket(fd, socket_map, handler):
163 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000164
165 Adapted from asyncore.read() et al.
166 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000167 xmpp_connection = socket_map.get(fd)
168 # This could happen if a previous handler call caused fd to get
169 # removed from socket_map.
170 if xmpp_connection is None:
171 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000172 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000173 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000174 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
175 raise
176 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000177 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000178
179 while True:
180 read_fds = [ self.fileno() ]
181 write_fds = []
182 exceptional_fds = []
183
184 for fd, xmpp_connection in self._xmpp_socket_map.items():
185 is_r = xmpp_connection.readable()
186 is_w = xmpp_connection.writable()
187 if is_r:
188 read_fds.append(fd)
189 if is_w:
190 write_fds.append(fd)
191 if is_r or is_w:
192 exceptional_fds.append(fd)
193
194 try:
195 read_fds, write_fds, exceptional_fds = (
196 select.select(read_fds, write_fds, exceptional_fds))
197 except select.error, err:
198 if err.args[0] != errno.EINTR:
199 raise
200 else:
201 continue
202
203 for fd in read_fds:
204 if fd == self.fileno():
205 self.HandleRequestNoBlock()
206 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000207 HandleXmppSocket(fd, self._xmpp_socket_map,
208 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000209
210 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000211 HandleXmppSocket(fd, self._xmpp_socket_map,
212 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000213
214 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000215 HandleXmppSocket(fd, self._xmpp_socket_map,
216 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000217
akalin@chromium.org154bb132010-11-12 02:20:27 +0000218
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000219class TCPEchoServer(SocketServer.TCPServer):
220 """A TCP echo server that echoes back what it has received."""
221
222 def server_bind(self):
223 """Override server_bind to store the server name."""
224 SocketServer.TCPServer.server_bind(self)
225 host, port = self.socket.getsockname()[:2]
226 self.server_name = socket.getfqdn(host)
227 self.server_port = port
228
229 def serve_forever(self):
230 self.stop = False
231 self.nonce_time = None
232 while not self.stop:
233 self.handle_request()
234 self.socket.close()
235
236
237class UDPEchoServer(SocketServer.UDPServer):
238 """A UDP echo server that echoes back what it has received."""
239
240 def server_bind(self):
241 """Override server_bind to store the server name."""
242 SocketServer.UDPServer.server_bind(self)
243 host, port = self.socket.getsockname()[:2]
244 self.server_name = socket.getfqdn(host)
245 self.server_port = port
246
247 def serve_forever(self):
248 self.stop = False
249 self.nonce_time = None
250 while not self.stop:
251 self.handle_request()
252 self.socket.close()
253
254
akalin@chromium.org154bb132010-11-12 02:20:27 +0000255class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
256
257 def __init__(self, request, client_address, socket_server,
258 connect_handlers, get_handlers, post_handlers, put_handlers):
259 self._connect_handlers = connect_handlers
260 self._get_handlers = get_handlers
261 self._post_handlers = post_handlers
262 self._put_handlers = put_handlers
263 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
264 self, request, client_address, socket_server)
265
266 def log_request(self, *args, **kwargs):
267 # Disable request logging to declutter test log output.
268 pass
269
270 def _ShouldHandleRequest(self, handler_name):
271 """Determines if the path can be handled by the handler.
272
273 We consider a handler valid if the path begins with the
274 handler name. It can optionally be followed by "?*", "/*".
275 """
276
277 pattern = re.compile('%s($|\?|/).*' % handler_name)
278 return pattern.match(self.path)
279
280 def do_CONNECT(self):
281 for handler in self._connect_handlers:
282 if handler():
283 return
284
285 def do_GET(self):
286 for handler in self._get_handlers:
287 if handler():
288 return
289
290 def do_POST(self):
291 for handler in self._post_handlers:
292 if handler():
293 return
294
295 def do_PUT(self):
296 for handler in self._put_handlers:
297 if handler():
298 return
299
300
301class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000302
303 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000304 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000305 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000306 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000307 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000308 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000309 self.NoCacheMaxAgeTimeHandler,
310 self.NoCacheTimeHandler,
311 self.CacheTimeHandler,
312 self.CacheExpiresHandler,
313 self.CacheProxyRevalidateHandler,
314 self.CachePrivateHandler,
315 self.CachePublicHandler,
316 self.CacheSMaxAgeHandler,
317 self.CacheMustRevalidateHandler,
318 self.CacheMustRevalidateMaxAgeHandler,
319 self.CacheNoStoreHandler,
320 self.CacheNoStoreMaxAgeHandler,
321 self.CacheNoTransformHandler,
322 self.DownloadHandler,
323 self.DownloadFinishHandler,
324 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000325 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000326 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000327 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000328 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000329 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.AuthBasicHandler,
331 self.AuthDigestHandler,
332 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000333 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000334 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000335 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000336 self.ServerRedirectHandler,
337 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000338 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000339 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000340 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000341 self.EchoTitleHandler,
342 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000343 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000344 self.DeviceManagementHandler] + get_handlers
345 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000346 self.EchoTitleHandler,
347 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000348 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000349
maruel@google.come250a9b2009-03-10 17:39:46 +0000350 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000351 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000352 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000353 'gif': 'image/gif',
354 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000355 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000356 'pdf' : 'application/pdf',
357 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 }
initial.commit94958cf2008-07-26 22:42:52 +0000359 self._default_mime_type = 'text/html'
360
akalin@chromium.org154bb132010-11-12 02:20:27 +0000361 BasePageHandler.__init__(self, request, client_address, socket_server,
362 connect_handlers, get_handlers, post_handlers,
363 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000364
initial.commit94958cf2008-07-26 22:42:52 +0000365 def GetMIMETypeFromName(self, file_name):
366 """Returns the mime type for the specified file_name. So far it only looks
367 at the file extension."""
368
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000369 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000370 if len(extension) == 0:
371 # no extension.
372 return self._default_mime_type
373
ericroman@google.comc17ca532009-05-07 03:51:05 +0000374 # extension starts with a dot, so we need to remove it
375 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000376
initial.commit94958cf2008-07-26 22:42:52 +0000377 def NoCacheMaxAgeTimeHandler(self):
378 """This request handler yields a page with the title set to the current
379 system time, and no caching requested."""
380
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000381 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000382 return False
383
384 self.send_response(200)
385 self.send_header('Cache-Control', 'max-age=0')
386 self.send_header('Content-type', 'text/html')
387 self.end_headers()
388
maruel@google.come250a9b2009-03-10 17:39:46 +0000389 self.wfile.write('<html><head><title>%s</title></head></html>' %
390 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000391
392 return True
393
394 def NoCacheTimeHandler(self):
395 """This request handler yields a page with the title set to the current
396 system time, and no caching requested."""
397
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000398 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000399 return False
400
401 self.send_response(200)
402 self.send_header('Cache-Control', 'no-cache')
403 self.send_header('Content-type', 'text/html')
404 self.end_headers()
405
maruel@google.come250a9b2009-03-10 17:39:46 +0000406 self.wfile.write('<html><head><title>%s</title></head></html>' %
407 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000408
409 return True
410
411 def CacheTimeHandler(self):
412 """This request handler yields a page with the title set to the current
413 system time, and allows caching for one minute."""
414
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000415 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000416 return False
417
418 self.send_response(200)
419 self.send_header('Cache-Control', 'max-age=60')
420 self.send_header('Content-type', 'text/html')
421 self.end_headers()
422
maruel@google.come250a9b2009-03-10 17:39:46 +0000423 self.wfile.write('<html><head><title>%s</title></head></html>' %
424 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000425
426 return True
427
428 def CacheExpiresHandler(self):
429 """This request handler yields a page with the title set to the current
430 system time, and set the page to expire on 1 Jan 2099."""
431
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000432 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000433 return False
434
435 self.send_response(200)
436 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
437 self.send_header('Content-type', 'text/html')
438 self.end_headers()
439
maruel@google.come250a9b2009-03-10 17:39:46 +0000440 self.wfile.write('<html><head><title>%s</title></head></html>' %
441 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000442
443 return True
444
445 def CacheProxyRevalidateHandler(self):
446 """This request handler yields a page with the title set to the current
447 system time, and allows caching for 60 seconds"""
448
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000449 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000450 return False
451
452 self.send_response(200)
453 self.send_header('Content-type', 'text/html')
454 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
455 self.end_headers()
456
maruel@google.come250a9b2009-03-10 17:39:46 +0000457 self.wfile.write('<html><head><title>%s</title></head></html>' %
458 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000459
460 return True
461
462 def CachePrivateHandler(self):
463 """This request handler yields a page with the title set to the current
464 system time, and allows caching for 5 seconds."""
465
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000466 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000467 return False
468
469 self.send_response(200)
470 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000471 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000472 self.end_headers()
473
maruel@google.come250a9b2009-03-10 17:39:46 +0000474 self.wfile.write('<html><head><title>%s</title></head></html>' %
475 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000476
477 return True
478
479 def CachePublicHandler(self):
480 """This request handler yields a page with the title set to the current
481 system time, and allows caching for 5 seconds."""
482
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000483 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000484 return False
485
486 self.send_response(200)
487 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000488 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000489 self.end_headers()
490
maruel@google.come250a9b2009-03-10 17:39:46 +0000491 self.wfile.write('<html><head><title>%s</title></head></html>' %
492 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000493
494 return True
495
496 def CacheSMaxAgeHandler(self):
497 """This request handler yields a page with the title set to the current
498 system time, and does not allow for caching."""
499
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000500 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000501 return False
502
503 self.send_response(200)
504 self.send_header('Content-type', 'text/html')
505 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
506 self.end_headers()
507
maruel@google.come250a9b2009-03-10 17:39:46 +0000508 self.wfile.write('<html><head><title>%s</title></head></html>' %
509 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000510
511 return True
512
513 def CacheMustRevalidateHandler(self):
514 """This request handler yields a page with the title set to the current
515 system time, and does not allow caching."""
516
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000517 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000518 return False
519
520 self.send_response(200)
521 self.send_header('Content-type', 'text/html')
522 self.send_header('Cache-Control', 'must-revalidate')
523 self.end_headers()
524
maruel@google.come250a9b2009-03-10 17:39:46 +0000525 self.wfile.write('<html><head><title>%s</title></head></html>' %
526 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000527
528 return True
529
530 def CacheMustRevalidateMaxAgeHandler(self):
531 """This request handler yields a page with the title set to the current
532 system time, and does not allow caching event though max-age of 60
533 seconds is specified."""
534
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000535 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000536 return False
537
538 self.send_response(200)
539 self.send_header('Content-type', 'text/html')
540 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
541 self.end_headers()
542
maruel@google.come250a9b2009-03-10 17:39:46 +0000543 self.wfile.write('<html><head><title>%s</title></head></html>' %
544 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000545
546 return True
547
initial.commit94958cf2008-07-26 22:42:52 +0000548 def CacheNoStoreHandler(self):
549 """This request handler yields a page with the title set to the current
550 system time, and does not allow the page to be stored."""
551
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000552 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000553 return False
554
555 self.send_response(200)
556 self.send_header('Content-type', 'text/html')
557 self.send_header('Cache-Control', 'no-store')
558 self.end_headers()
559
maruel@google.come250a9b2009-03-10 17:39:46 +0000560 self.wfile.write('<html><head><title>%s</title></head></html>' %
561 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000562
563 return True
564
565 def CacheNoStoreMaxAgeHandler(self):
566 """This request handler yields a page with the title set to the current
567 system time, and does not allow the page to be stored even though max-age
568 of 60 seconds is specified."""
569
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000570 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000571 return False
572
573 self.send_response(200)
574 self.send_header('Content-type', 'text/html')
575 self.send_header('Cache-Control', 'max-age=60, no-store')
576 self.end_headers()
577
maruel@google.come250a9b2009-03-10 17:39:46 +0000578 self.wfile.write('<html><head><title>%s</title></head></html>' %
579 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000580
581 return True
582
583
584 def CacheNoTransformHandler(self):
585 """This request handler yields a page with the title set to the current
586 system time, and does not allow the content to transformed during
587 user-agent caching"""
588
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000589 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000590 return False
591
592 self.send_response(200)
593 self.send_header('Content-type', 'text/html')
594 self.send_header('Cache-Control', 'no-transform')
595 self.end_headers()
596
maruel@google.come250a9b2009-03-10 17:39:46 +0000597 self.wfile.write('<html><head><title>%s</title></head></html>' %
598 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000599
600 return True
601
602 def EchoHeader(self):
603 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000604 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000605
ananta@chromium.org56812d02011-04-07 17:52:05 +0000606 """This function echoes back the value of a specific request header"""
607 """while allowing caching for 16 hours."""
608 def EchoHeaderCache(self):
609 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000610
611 def EchoHeaderHelper(self, echo_header):
612 """This function echoes back the value of the request header passed in."""
613 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000614 return False
615
616 query_char = self.path.find('?')
617 if query_char != -1:
618 header_name = self.path[query_char+1:]
619
620 self.send_response(200)
621 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000622 if echo_header == '/echoheadercache':
623 self.send_header('Cache-control', 'max-age=60000')
624 else:
625 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000626 # insert a vary header to properly indicate that the cachability of this
627 # request is subject to value of the request header being echoed.
628 if len(header_name) > 0:
629 self.send_header('Vary', header_name)
630 self.end_headers()
631
632 if len(header_name) > 0:
633 self.wfile.write(self.headers.getheader(header_name))
634
635 return True
636
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000637 def ReadRequestBody(self):
638 """This function reads the body of the current HTTP request, handling
639 both plain and chunked transfer encoded requests."""
640
641 if self.headers.getheader('transfer-encoding') != 'chunked':
642 length = int(self.headers.getheader('content-length'))
643 return self.rfile.read(length)
644
645 # Read the request body as chunks.
646 body = ""
647 while True:
648 line = self.rfile.readline()
649 length = int(line, 16)
650 if length == 0:
651 self.rfile.readline()
652 break
653 body += self.rfile.read(length)
654 self.rfile.read(2)
655 return body
656
initial.commit94958cf2008-07-26 22:42:52 +0000657 def EchoHandler(self):
658 """This handler just echoes back the payload of the request, for testing
659 form submission."""
660
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000661 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000662 return False
663
664 self.send_response(200)
665 self.send_header('Content-type', 'text/html')
666 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000667 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000668 return True
669
670 def EchoTitleHandler(self):
671 """This handler is like Echo, but sets the page title to the request."""
672
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000673 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000674 return False
675
676 self.send_response(200)
677 self.send_header('Content-type', 'text/html')
678 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000679 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000680 self.wfile.write('<html><head><title>')
681 self.wfile.write(request)
682 self.wfile.write('</title></head></html>')
683 return True
684
685 def EchoAllHandler(self):
686 """This handler yields a (more) human-readable page listing information
687 about the request header & contents."""
688
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000689 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000690 return False
691
692 self.send_response(200)
693 self.send_header('Content-type', 'text/html')
694 self.end_headers()
695 self.wfile.write('<html><head><style>'
696 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
697 '</style></head><body>'
698 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000699 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000700 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000701
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000702 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000703 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000704 params = cgi.parse_qs(qs, keep_blank_values=1)
705
706 for param in params:
707 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000708
709 self.wfile.write('</pre>')
710
711 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
712
713 self.wfile.write('</body></html>')
714 return True
715
716 def DownloadHandler(self):
717 """This handler sends a downloadable file with or without reporting
718 the size (6K)."""
719
720 if self.path.startswith("/download-unknown-size"):
721 send_length = False
722 elif self.path.startswith("/download-known-size"):
723 send_length = True
724 else:
725 return False
726
727 #
728 # The test which uses this functionality is attempting to send
729 # small chunks of data to the client. Use a fairly large buffer
730 # so that we'll fill chrome's IO buffer enough to force it to
731 # actually write the data.
732 # See also the comments in the client-side of this test in
733 # download_uitest.cc
734 #
735 size_chunk1 = 35*1024
736 size_chunk2 = 10*1024
737
738 self.send_response(200)
739 self.send_header('Content-type', 'application/octet-stream')
740 self.send_header('Cache-Control', 'max-age=0')
741 if send_length:
742 self.send_header('Content-Length', size_chunk1 + size_chunk2)
743 self.end_headers()
744
745 # First chunk of data:
746 self.wfile.write("*" * size_chunk1)
747 self.wfile.flush()
748
749 # handle requests until one of them clears this flag.
750 self.server.waitForDownload = True
751 while self.server.waitForDownload:
752 self.server.handle_request()
753
754 # Second chunk of data:
755 self.wfile.write("*" * size_chunk2)
756 return True
757
758 def DownloadFinishHandler(self):
759 """This handler just tells the server to finish the current download."""
760
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000761 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000762 return False
763
764 self.server.waitForDownload = False
765 self.send_response(200)
766 self.send_header('Content-type', 'text/html')
767 self.send_header('Cache-Control', 'max-age=0')
768 self.end_headers()
769 return True
770
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000771 def _ReplaceFileData(self, data, query_parameters):
772 """Replaces matching substrings in a file.
773
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000774 If the 'replace_text' URL query parameter is present, it is expected to be
775 of the form old_text:new_text, which indicates that any old_text strings in
776 the file are replaced with new_text. Multiple 'replace_text' parameters may
777 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000778
779 If the parameters are not present, |data| is returned.
780 """
781 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000782 replace_text_values = query_dict.get('replace_text', [])
783 for replace_text_value in replace_text_values:
784 replace_text_args = replace_text_value.split(':')
785 if len(replace_text_args) != 2:
786 raise ValueError(
787 'replace_text must be of form old_text:new_text. Actual value: %s' %
788 replace_text_value)
789 old_text_b64, new_text_b64 = replace_text_args
790 old_text = base64.urlsafe_b64decode(old_text_b64)
791 new_text = base64.urlsafe_b64decode(new_text_b64)
792 data = data.replace(old_text, new_text)
793 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000794
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000795 def ZipFileHandler(self):
796 """This handler sends the contents of the requested file in compressed form.
797 Can pass in a parameter that specifies that the content length be
798 C - the compressed size (OK),
799 U - the uncompressed size (Non-standard, but handled),
800 S - less than compressed (OK because we keep going),
801 M - larger than compressed but less than uncompressed (an error),
802 L - larger than uncompressed (an error)
803 Example: compressedfiles/Picture_1.doc?C
804 """
805
806 prefix = "/compressedfiles/"
807 if not self.path.startswith(prefix):
808 return False
809
810 # Consume a request body if present.
811 if self.command == 'POST' or self.command == 'PUT' :
812 self.ReadRequestBody()
813
814 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
815
816 if not query in ('C', 'U', 'S', 'M', 'L'):
817 return False
818
819 sub_path = url_path[len(prefix):]
820 entries = sub_path.split('/')
821 file_path = os.path.join(self.server.data_dir, *entries)
822 if os.path.isdir(file_path):
823 file_path = os.path.join(file_path, 'index.html')
824
825 if not os.path.isfile(file_path):
826 print "File not found " + sub_path + " full path:" + file_path
827 self.send_error(404)
828 return True
829
830 f = open(file_path, "rb")
831 data = f.read()
832 uncompressed_len = len(data)
833 f.close()
834
835 # Compress the data.
836 data = zlib.compress(data)
837 compressed_len = len(data)
838
839 content_length = compressed_len
840 if query == 'U':
841 content_length = uncompressed_len
842 elif query == 'S':
843 content_length = compressed_len / 2
844 elif query == 'M':
845 content_length = (compressed_len + uncompressed_len) / 2
846 elif query == 'L':
847 content_length = compressed_len + uncompressed_len
848
849 self.send_response(200)
850 self.send_header('Content-type', 'application/msword')
851 self.send_header('Content-encoding', 'deflate')
852 self.send_header('Connection', 'close')
853 self.send_header('Content-Length', content_length)
854 self.send_header('ETag', '\'' + file_path + '\'')
855 self.end_headers()
856
857 self.wfile.write(data)
858
859 return True
860
initial.commit94958cf2008-07-26 22:42:52 +0000861 def FileHandler(self):
862 """This handler sends the contents of the requested file. Wow, it's like
863 a real webserver!"""
864
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000865 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000866 if not self.path.startswith(prefix):
867 return False
868
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000869 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000870 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000871 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000872
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000873 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
874 sub_path = url_path[len(prefix):]
875 entries = sub_path.split('/')
876 file_path = os.path.join(self.server.data_dir, *entries)
877 if os.path.isdir(file_path):
878 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000879
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000880 if not os.path.isfile(file_path):
881 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000882 self.send_error(404)
883 return True
884
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000885 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000886 data = f.read()
887 f.close()
888
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000889 data = self._ReplaceFileData(data, query)
890
initial.commit94958cf2008-07-26 22:42:52 +0000891 # If file.mock-http-headers exists, it contains the headers we
892 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000893 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000894 if os.path.isfile(headers_path):
895 f = open(headers_path, "r")
896
897 # "HTTP/1.1 200 OK"
898 response = f.readline()
899 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
900 self.send_response(int(status_code))
901
902 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000903 header_values = re.findall('(\S+):\s*(.*)', line)
904 if len(header_values) > 0:
905 # "name: value"
906 name, value = header_values[0]
907 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000908 f.close()
909 else:
910 # Could be more generic once we support mime-type sniffing, but for
911 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000912
913 range = self.headers.get('Range')
914 if range and range.startswith('bytes='):
915 # Note this doesn't handle all valid byte range values (i.e. open ended
916 # ones), just enough for what we needed so far.
917 range = range[6:].split('-')
918 start = int(range[0])
919 end = int(range[1])
920
921 self.send_response(206)
922 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
923 str(len(data))
924 self.send_header('Content-Range', content_range)
925 data = data[start: end + 1]
926 else:
927 self.send_response(200)
928
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000929 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000930 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000931 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000932 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000933 self.end_headers()
934
935 self.wfile.write(data)
936
937 return True
938
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000939 def SetCookieHandler(self):
940 """This handler just sets a cookie, for testing cookie handling."""
941
942 if not self._ShouldHandleRequest("/set-cookie"):
943 return False
944
945 query_char = self.path.find('?')
946 if query_char != -1:
947 cookie_values = self.path[query_char + 1:].split('&')
948 else:
949 cookie_values = ("",)
950 self.send_response(200)
951 self.send_header('Content-type', 'text/html')
952 for cookie_value in cookie_values:
953 self.send_header('Set-Cookie', '%s' % cookie_value)
954 self.end_headers()
955 for cookie_value in cookie_values:
956 self.wfile.write('%s' % cookie_value)
957 return True
958
initial.commit94958cf2008-07-26 22:42:52 +0000959 def AuthBasicHandler(self):
960 """This handler tests 'Basic' authentication. It just sends a page with
961 title 'user/pass' if you succeed."""
962
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000963 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000964 return False
965
966 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000967 expected_password = 'secret'
968 realm = 'testrealm'
969 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000970
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000971 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
972 query_params = cgi.parse_qs(query, True)
973 if 'set-cookie-if-challenged' in query_params:
974 set_cookie_if_challenged = True
975 if 'password' in query_params:
976 expected_password = query_params['password'][0]
977 if 'realm' in query_params:
978 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000979
initial.commit94958cf2008-07-26 22:42:52 +0000980 auth = self.headers.getheader('authorization')
981 try:
982 if not auth:
983 raise Exception('no auth')
984 b64str = re.findall(r'Basic (\S+)', auth)[0]
985 userpass = base64.b64decode(b64str)
986 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000987 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000988 raise Exception('wrong password')
989 except Exception, e:
990 # Authentication failed.
991 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000992 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +0000993 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000994 if set_cookie_if_challenged:
995 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000996 self.end_headers()
997 self.wfile.write('<html><head>')
998 self.wfile.write('<title>Denied: %s</title>' % e)
999 self.wfile.write('</head><body>')
1000 self.wfile.write('auth=%s<p>' % auth)
1001 self.wfile.write('b64str=%s<p>' % b64str)
1002 self.wfile.write('username: %s<p>' % username)
1003 self.wfile.write('userpass: %s<p>' % userpass)
1004 self.wfile.write('password: %s<p>' % password)
1005 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1006 self.wfile.write('</body></html>')
1007 return True
1008
1009 # Authentication successful. (Return a cachable response to allow for
1010 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001011 old_protocol_version = self.protocol_version
1012 self.protocol_version = "HTTP/1.1"
1013
initial.commit94958cf2008-07-26 22:42:52 +00001014 if_none_match = self.headers.getheader('if-none-match')
1015 if if_none_match == "abc":
1016 self.send_response(304)
1017 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001018 elif url_path.endswith(".gif"):
1019 # Using chrome/test/data/google/logo.gif as the test image
1020 test_image_path = ['google', 'logo.gif']
1021 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1022 if not os.path.isfile(gif_path):
1023 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001024 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001025 return True
1026
1027 f = open(gif_path, "rb")
1028 data = f.read()
1029 f.close()
1030
1031 self.send_response(200)
1032 self.send_header('Content-type', 'image/gif')
1033 self.send_header('Cache-control', 'max-age=60000')
1034 self.send_header('Etag', 'abc')
1035 self.end_headers()
1036 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001037 else:
1038 self.send_response(200)
1039 self.send_header('Content-type', 'text/html')
1040 self.send_header('Cache-control', 'max-age=60000')
1041 self.send_header('Etag', 'abc')
1042 self.end_headers()
1043 self.wfile.write('<html><head>')
1044 self.wfile.write('<title>%s/%s</title>' % (username, password))
1045 self.wfile.write('</head><body>')
1046 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001047 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001048 self.wfile.write('</body></html>')
1049
rvargas@google.com54453b72011-05-19 01:11:11 +00001050 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001051 return True
1052
tonyg@chromium.org75054202010-03-31 22:06:10 +00001053 def GetNonce(self, force_reset=False):
1054 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001055
tonyg@chromium.org75054202010-03-31 22:06:10 +00001056 This is a fake implementation. A real implementation would only use a given
1057 nonce a single time (hence the name n-once). However, for the purposes of
1058 unittesting, we don't care about the security of the nonce.
1059
1060 Args:
1061 force_reset: Iff set, the nonce will be changed. Useful for testing the
1062 "stale" response.
1063 """
1064 if force_reset or not self.server.nonce_time:
1065 self.server.nonce_time = time.time()
1066 return _new_md5('privatekey%s%d' %
1067 (self.path, self.server.nonce_time)).hexdigest()
1068
1069 def AuthDigestHandler(self):
1070 """This handler tests 'Digest' authentication.
1071
1072 It just sends a page with title 'user/pass' if you succeed.
1073
1074 A stale response is sent iff "stale" is present in the request path.
1075 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001076 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001077 return False
1078
tonyg@chromium.org75054202010-03-31 22:06:10 +00001079 stale = 'stale' in self.path
1080 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001081 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001082 password = 'secret'
1083 realm = 'testrealm'
1084
1085 auth = self.headers.getheader('authorization')
1086 pairs = {}
1087 try:
1088 if not auth:
1089 raise Exception('no auth')
1090 if not auth.startswith('Digest'):
1091 raise Exception('not digest')
1092 # Pull out all the name="value" pairs as a dictionary.
1093 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1094
1095 # Make sure it's all valid.
1096 if pairs['nonce'] != nonce:
1097 raise Exception('wrong nonce')
1098 if pairs['opaque'] != opaque:
1099 raise Exception('wrong opaque')
1100
1101 # Check the 'response' value and make sure it matches our magic hash.
1102 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001103 hash_a1 = _new_md5(
1104 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001105 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001106 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001107 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001108 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1109 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001110 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001111
1112 if pairs['response'] != response:
1113 raise Exception('wrong password')
1114 except Exception, e:
1115 # Authentication failed.
1116 self.send_response(401)
1117 hdr = ('Digest '
1118 'realm="%s", '
1119 'domain="/", '
1120 'qop="auth", '
1121 'algorithm=MD5, '
1122 'nonce="%s", '
1123 'opaque="%s"') % (realm, nonce, opaque)
1124 if stale:
1125 hdr += ', stale="TRUE"'
1126 self.send_header('WWW-Authenticate', hdr)
1127 self.send_header('Content-type', 'text/html')
1128 self.end_headers()
1129 self.wfile.write('<html><head>')
1130 self.wfile.write('<title>Denied: %s</title>' % e)
1131 self.wfile.write('</head><body>')
1132 self.wfile.write('auth=%s<p>' % auth)
1133 self.wfile.write('pairs=%s<p>' % pairs)
1134 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1135 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1136 self.wfile.write('</body></html>')
1137 return True
1138
1139 # Authentication successful.
1140 self.send_response(200)
1141 self.send_header('Content-type', 'text/html')
1142 self.end_headers()
1143 self.wfile.write('<html><head>')
1144 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1145 self.wfile.write('</head><body>')
1146 self.wfile.write('auth=%s<p>' % auth)
1147 self.wfile.write('pairs=%s<p>' % pairs)
1148 self.wfile.write('</body></html>')
1149
1150 return True
1151
1152 def SlowServerHandler(self):
1153 """Wait for the user suggested time before responding. The syntax is
1154 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001155 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001156 return False
1157 query_char = self.path.find('?')
1158 wait_sec = 1.0
1159 if query_char >= 0:
1160 try:
1161 wait_sec = int(self.path[query_char + 1:])
1162 except ValueError:
1163 pass
1164 time.sleep(wait_sec)
1165 self.send_response(200)
1166 self.send_header('Content-type', 'text/plain')
1167 self.end_headers()
1168 self.wfile.write("waited %d seconds" % wait_sec)
1169 return True
1170
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001171 def ChunkedServerHandler(self):
1172 """Send chunked response. Allows to specify chunks parameters:
1173 - waitBeforeHeaders - ms to wait before sending headers
1174 - waitBetweenChunks - ms to wait between chunks
1175 - chunkSize - size of each chunk in bytes
1176 - chunksNumber - number of chunks
1177 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1178 waits one second, then sends headers and five chunks five bytes each."""
1179 if not self._ShouldHandleRequest("/chunked"):
1180 return False
1181 query_char = self.path.find('?')
1182 chunkedSettings = {'waitBeforeHeaders' : 0,
1183 'waitBetweenChunks' : 0,
1184 'chunkSize' : 5,
1185 'chunksNumber' : 5}
1186 if query_char >= 0:
1187 params = self.path[query_char + 1:].split('&')
1188 for param in params:
1189 keyValue = param.split('=')
1190 if len(keyValue) == 2:
1191 try:
1192 chunkedSettings[keyValue[0]] = int(keyValue[1])
1193 except ValueError:
1194 pass
1195 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1196 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1197 self.send_response(200)
1198 self.send_header('Content-type', 'text/plain')
1199 self.send_header('Connection', 'close')
1200 self.send_header('Transfer-Encoding', 'chunked')
1201 self.end_headers()
1202 # Chunked encoding: sending all chunks, then final zero-length chunk and
1203 # then final CRLF.
1204 for i in range(0, chunkedSettings['chunksNumber']):
1205 if i > 0:
1206 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1207 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1208 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1209 self.sendChunkHelp('')
1210 return True
1211
initial.commit94958cf2008-07-26 22:42:52 +00001212 def ContentTypeHandler(self):
1213 """Returns a string of html with the given content type. E.g.,
1214 /contenttype?text/css returns an html file with the Content-Type
1215 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001216 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001217 return False
1218 query_char = self.path.find('?')
1219 content_type = self.path[query_char + 1:].strip()
1220 if not content_type:
1221 content_type = 'text/html'
1222 self.send_response(200)
1223 self.send_header('Content-Type', content_type)
1224 self.end_headers()
1225 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1226 return True
1227
creis@google.com2f4f6a42011-03-25 19:44:19 +00001228 def NoContentHandler(self):
1229 """Returns a 204 No Content response."""
1230 if not self._ShouldHandleRequest("/nocontent"):
1231 return False
1232 self.send_response(204)
1233 self.end_headers()
1234 return True
1235
initial.commit94958cf2008-07-26 22:42:52 +00001236 def ServerRedirectHandler(self):
1237 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001238 '/server-redirect?http://foo.bar/asdf' to redirect to
1239 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001240
1241 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001242 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001243 return False
1244
1245 query_char = self.path.find('?')
1246 if query_char < 0 or len(self.path) <= query_char + 1:
1247 self.sendRedirectHelp(test_name)
1248 return True
1249 dest = self.path[query_char + 1:]
1250
1251 self.send_response(301) # moved permanently
1252 self.send_header('Location', dest)
1253 self.send_header('Content-type', 'text/html')
1254 self.end_headers()
1255 self.wfile.write('<html><head>')
1256 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1257
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001258 return True
initial.commit94958cf2008-07-26 22:42:52 +00001259
1260 def ClientRedirectHandler(self):
1261 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001262 '/client-redirect?http://foo.bar/asdf' to redirect to
1263 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001264
1265 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001266 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001267 return False
1268
1269 query_char = self.path.find('?');
1270 if query_char < 0 or len(self.path) <= query_char + 1:
1271 self.sendRedirectHelp(test_name)
1272 return True
1273 dest = self.path[query_char + 1:]
1274
1275 self.send_response(200)
1276 self.send_header('Content-type', 'text/html')
1277 self.end_headers()
1278 self.wfile.write('<html><head>')
1279 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1280 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1281
1282 return True
1283
tony@chromium.org03266982010-03-05 03:18:42 +00001284 def MultipartHandler(self):
1285 """Send a multipart response (10 text/html pages)."""
1286 test_name = "/multipart"
1287 if not self._ShouldHandleRequest(test_name):
1288 return False
1289
1290 num_frames = 10
1291 bound = '12345'
1292 self.send_response(200)
1293 self.send_header('Content-type',
1294 'multipart/x-mixed-replace;boundary=' + bound)
1295 self.end_headers()
1296
1297 for i in xrange(num_frames):
1298 self.wfile.write('--' + bound + '\r\n')
1299 self.wfile.write('Content-type: text/html\r\n\r\n')
1300 self.wfile.write('<title>page ' + str(i) + '</title>')
1301 self.wfile.write('page ' + str(i))
1302
1303 self.wfile.write('--' + bound + '--')
1304 return True
1305
initial.commit94958cf2008-07-26 22:42:52 +00001306 def DefaultResponseHandler(self):
1307 """This is the catch-all response handler for requests that aren't handled
1308 by one of the special handlers above.
1309 Note that we specify the content-length as without it the https connection
1310 is not closed properly (and the browser keeps expecting data)."""
1311
1312 contents = "Default response given for path: " + self.path
1313 self.send_response(200)
1314 self.send_header('Content-type', 'text/html')
1315 self.send_header("Content-Length", len(contents))
1316 self.end_headers()
1317 self.wfile.write(contents)
1318 return True
1319
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001320 def RedirectConnectHandler(self):
1321 """Sends a redirect to the CONNECT request for www.redirect.com. This
1322 response is not specified by the RFC, so the browser should not follow
1323 the redirect."""
1324
1325 if (self.path.find("www.redirect.com") < 0):
1326 return False
1327
1328 dest = "http://www.destination.com/foo.js"
1329
1330 self.send_response(302) # moved temporarily
1331 self.send_header('Location', dest)
1332 self.send_header('Connection', 'close')
1333 self.end_headers()
1334 return True
1335
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001336 def ServerAuthConnectHandler(self):
1337 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1338 response doesn't make sense because the proxy server cannot request
1339 server authentication."""
1340
1341 if (self.path.find("www.server-auth.com") < 0):
1342 return False
1343
1344 challenge = 'Basic realm="WallyWorld"'
1345
1346 self.send_response(401) # unauthorized
1347 self.send_header('WWW-Authenticate', challenge)
1348 self.send_header('Connection', 'close')
1349 self.end_headers()
1350 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001351
1352 def DefaultConnectResponseHandler(self):
1353 """This is the catch-all response handler for CONNECT requests that aren't
1354 handled by one of the special handlers above. Real Web servers respond
1355 with 400 to CONNECT requests."""
1356
1357 contents = "Your client has issued a malformed or illegal request."
1358 self.send_response(400) # bad request
1359 self.send_header('Content-type', 'text/html')
1360 self.send_header("Content-Length", len(contents))
1361 self.end_headers()
1362 self.wfile.write(contents)
1363 return True
1364
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001365 def DeviceManagementHandler(self):
1366 """Delegates to the device management service used for cloud policy."""
1367 if not self._ShouldHandleRequest("/device_management"):
1368 return False
1369
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001370 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001371
1372 if not self.server._device_management_handler:
1373 import device_management
1374 policy_path = os.path.join(self.server.data_dir, 'device_management')
1375 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001376 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001377 self.server.policy_keys,
1378 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001379
1380 http_response, raw_reply = (
1381 self.server._device_management_handler.HandleRequest(self.path,
1382 self.headers,
1383 raw_request))
1384 self.send_response(http_response)
1385 self.end_headers()
1386 self.wfile.write(raw_reply)
1387 return True
1388
initial.commit94958cf2008-07-26 22:42:52 +00001389 # called by the redirect handling function when there is no parameter
1390 def sendRedirectHelp(self, redirect_name):
1391 self.send_response(200)
1392 self.send_header('Content-type', 'text/html')
1393 self.end_headers()
1394 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1395 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1396 self.wfile.write('</body></html>')
1397
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001398 # called by chunked handling function
1399 def sendChunkHelp(self, chunk):
1400 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1401 self.wfile.write('%X\r\n' % len(chunk))
1402 self.wfile.write(chunk)
1403 self.wfile.write('\r\n')
1404
akalin@chromium.org154bb132010-11-12 02:20:27 +00001405
1406class SyncPageHandler(BasePageHandler):
1407 """Handler for the main HTTP sync server."""
1408
1409 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001410 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001411 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001412 self.ChromiumSyncDisableNotificationsOpHandler,
1413 self.ChromiumSyncEnableNotificationsOpHandler,
1414 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001415 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001416 self.ChromiumSyncTransientErrorOpHandler,
1417 self.ChromiumSyncSyncTabsOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001418
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001419 post_handlers = [self.ChromiumSyncCommandHandler,
1420 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001421 BasePageHandler.__init__(self, request, client_address,
1422 sync_http_server, [], get_handlers,
1423 post_handlers, [])
1424
1425 def ChromiumSyncTimeHandler(self):
1426 """Handle Chromium sync .../time requests.
1427
1428 The syncer sometimes checks server reachability by examining /time.
1429 """
1430 test_name = "/chromiumsync/time"
1431 if not self._ShouldHandleRequest(test_name):
1432 return False
1433
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001434 # Chrome hates it if we send a response before reading the request.
1435 if self.headers.getheader('content-length'):
1436 length = int(self.headers.getheader('content-length'))
1437 raw_request = self.rfile.read(length)
1438
akalin@chromium.org154bb132010-11-12 02:20:27 +00001439 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001440 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001441 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001442 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001443 return True
1444
1445 def ChromiumSyncCommandHandler(self):
1446 """Handle a chromiumsync command arriving via http.
1447
1448 This covers all sync protocol commands: authentication, getupdates, and
1449 commit.
1450 """
1451 test_name = "/chromiumsync/command"
1452 if not self._ShouldHandleRequest(test_name):
1453 return False
1454
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001455 length = int(self.headers.getheader('content-length'))
1456 raw_request = self.rfile.read(length)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001457
1458 http_response, raw_reply = self.server.HandleCommand(
1459 self.path, raw_request)
1460 self.send_response(http_response)
1461 self.end_headers()
1462 self.wfile.write(raw_reply)
1463 return True
1464
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001465 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001466 test_name = "/chromiumsync/migrate"
1467 if not self._ShouldHandleRequest(test_name):
1468 return False
1469
1470 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1471 self.path)
1472 self.send_response(http_response)
1473 self.send_header('Content-Type', 'text/html')
1474 self.send_header('Content-Length', len(raw_reply))
1475 self.end_headers()
1476 self.wfile.write(raw_reply)
1477 return True
1478
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001479 def ChromiumSyncDisableNotificationsOpHandler(self):
1480 test_name = "/chromiumsync/disablenotifications"
1481 if not self._ShouldHandleRequest(test_name):
1482 return False
1483 self.server.GetXmppServer().DisableNotifications()
1484 result = 200
1485 raw_reply = ('<html><title>Notifications disabled</title>'
1486 '<H1>Notifications disabled</H1></html>')
1487 self.send_response(result)
1488 self.send_header('Content-Type', 'text/html')
1489 self.send_header('Content-Length', len(raw_reply))
1490 self.end_headers()
1491 self.wfile.write(raw_reply)
1492 return True;
1493
1494 def ChromiumSyncEnableNotificationsOpHandler(self):
1495 test_name = "/chromiumsync/enablenotifications"
1496 if not self._ShouldHandleRequest(test_name):
1497 return False
1498 self.server.GetXmppServer().EnableNotifications()
1499 result = 200
1500 raw_reply = ('<html><title>Notifications enabled</title>'
1501 '<H1>Notifications enabled</H1></html>')
1502 self.send_response(result)
1503 self.send_header('Content-Type', 'text/html')
1504 self.send_header('Content-Length', len(raw_reply))
1505 self.end_headers()
1506 self.wfile.write(raw_reply)
1507 return True;
1508
1509 def ChromiumSyncSendNotificationOpHandler(self):
1510 test_name = "/chromiumsync/sendnotification"
1511 if not self._ShouldHandleRequest(test_name):
1512 return False
1513 query = urlparse.urlparse(self.path)[4]
1514 query_params = urlparse.parse_qs(query)
1515 channel = ''
1516 data = ''
1517 if 'channel' in query_params:
1518 channel = query_params['channel'][0]
1519 if 'data' in query_params:
1520 data = query_params['data'][0]
1521 self.server.GetXmppServer().SendNotification(channel, data)
1522 result = 200
1523 raw_reply = ('<html><title>Notification sent</title>'
1524 '<H1>Notification sent with channel "%s" '
1525 'and data "%s"</H1></html>'
1526 % (channel, data))
1527 self.send_response(result)
1528 self.send_header('Content-Type', 'text/html')
1529 self.send_header('Content-Length', len(raw_reply))
1530 self.end_headers()
1531 self.wfile.write(raw_reply)
1532 return True;
1533
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001534 def ChromiumSyncBirthdayErrorOpHandler(self):
1535 test_name = "/chromiumsync/birthdayerror"
1536 if not self._ShouldHandleRequest(test_name):
1537 return False
1538 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1539 self.send_response(result)
1540 self.send_header('Content-Type', 'text/html')
1541 self.send_header('Content-Length', len(raw_reply))
1542 self.end_headers()
1543 self.wfile.write(raw_reply)
1544 return True;
1545
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001546 def ChromiumSyncTransientErrorOpHandler(self):
1547 test_name = "/chromiumsync/transienterror"
1548 if not self._ShouldHandleRequest(test_name):
1549 return False
1550 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1551 self.send_response(result)
1552 self.send_header('Content-Type', 'text/html')
1553 self.send_header('Content-Length', len(raw_reply))
1554 self.end_headers()
1555 self.wfile.write(raw_reply)
1556 return True;
1557
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001558 def ChromiumSyncSyncTabsOpHandler(self):
1559 test_name = "/chromiumsync/synctabs"
1560 if not self._ShouldHandleRequest(test_name):
1561 return False
1562 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1563 self.send_response(result)
1564 self.send_header('Content-Type', 'text/html')
1565 self.send_header('Content-Length', len(raw_reply))
1566 self.end_headers()
1567 self.wfile.write(raw_reply)
1568 return True;
1569
akalin@chromium.org154bb132010-11-12 02:20:27 +00001570
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001571def MakeDataDir():
1572 if options.data_dir:
1573 if not os.path.isdir(options.data_dir):
1574 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1575 return None
1576 my_data_dir = options.data_dir
1577 else:
1578 # Create the default path to our data dir, relative to the exe dir.
1579 my_data_dir = os.path.dirname(sys.argv[0])
1580 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001581 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001582
1583 #TODO(ibrar): Must use Find* funtion defined in google\tools
1584 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1585
1586 return my_data_dir
1587
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001588
1589class TCPEchoHandler(SocketServer.BaseRequestHandler):
1590 """The RequestHandler class for TCP echo server.
1591
1592 It is instantiated once per connection to the server, and overrides the
1593 handle() method to implement communication to the client.
1594 """
1595
1596 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001597 """Handles the request from the client and constructs a response."""
1598
1599 data = self.request.recv(65536).strip()
1600 # Verify the "echo request" message received from the client. Send back
1601 # "echo response" message if "echo request" message is valid.
1602 try:
1603 return_data = echo_message.GetEchoResponseData(data)
1604 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001605 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001606 except ValueError:
1607 return
1608
1609 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001610
1611
1612class UDPEchoHandler(SocketServer.BaseRequestHandler):
1613 """The RequestHandler class for UDP echo server.
1614
1615 It is instantiated once per connection to the server, and overrides the
1616 handle() method to implement communication to the client.
1617 """
1618
1619 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001620 """Handles the request from the client and constructs a response."""
1621
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001622 data = self.request[0].strip()
1623 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001624 # Verify the "echo request" message received from the client. Send back
1625 # "echo response" message if "echo request" message is valid.
1626 try:
1627 return_data = echo_message.GetEchoResponseData(data)
1628 if not return_data:
1629 return
1630 except ValueError:
1631 return
1632 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001633
1634
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001635class FileMultiplexer:
1636 def __init__(self, fd1, fd2) :
1637 self.__fd1 = fd1
1638 self.__fd2 = fd2
1639
1640 def __del__(self) :
1641 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1642 self.__fd1.close()
1643 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1644 self.__fd2.close()
1645
1646 def write(self, text) :
1647 self.__fd1.write(text)
1648 self.__fd2.write(text)
1649
1650 def flush(self) :
1651 self.__fd1.flush()
1652 self.__fd2.flush()
1653
initial.commit94958cf2008-07-26 22:42:52 +00001654def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001655 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001656 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001657 if options.log_to_console:
1658 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1659 else:
1660 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001661
1662 port = options.port
1663
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001664 server_data = {}
1665
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001666 if options.server_type == SERVER_HTTP:
1667 if options.cert:
1668 # let's make sure the cert file exists.
1669 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001670 print 'specified server cert file not found: ' + options.cert + \
1671 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001672 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001673 for ca_cert in options.ssl_client_ca:
1674 if not os.path.isfile(ca_cert):
1675 print 'specified trusted client CA file not found: ' + ca_cert + \
1676 ' exiting...'
1677 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001678 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001679 options.ssl_client_auth, options.ssl_client_ca,
1680 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001681 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001682 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001683 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001684 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001685
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001686 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001687 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001688 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001689 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001690 server.policy_keys = options.policy_keys
1691 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001692 elif options.server_type == SERVER_SYNC:
1693 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1694 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001695 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1696 server_data['port'] = server.server_port
1697 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001698 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001699 # Used for generating the key (randomly) that encodes the "echo request"
1700 # message.
1701 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001702 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1703 print 'Echo TCP server started on port %d...' % server.server_port
1704 server_data['port'] = server.server_port
1705 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001706 # Used for generating the key (randomly) that encodes the "echo request"
1707 # message.
1708 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001709 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1710 print 'Echo UDP server started on port %d...' % server.server_port
1711 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001712 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001713 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001714 my_data_dir = MakeDataDir()
1715
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001716 # Instantiate a dummy authorizer for managing 'virtual' users
1717 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1718
1719 # Define a new user having full r/w permissions and a read-only
1720 # anonymous user
1721 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1722
1723 authorizer.add_anonymous(my_data_dir)
1724
1725 # Instantiate FTP handler class
1726 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1727 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001728
1729 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001730 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1731 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001732
1733 # Instantiate FTP server class and listen to 127.0.0.1:port
1734 address = ('127.0.0.1', port)
1735 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001736 server_data['port'] = server.socket.getsockname()[1]
1737 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001738
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001739 # Notify the parent that we've started. (BaseServer subclasses
1740 # bind their sockets on construction.)
1741 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001742 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001743 server_data_len = len(server_data_json)
1744 print 'sending server_data: %s (%d bytes)' % (
1745 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001746 if sys.platform == 'win32':
1747 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1748 else:
1749 fd = options.startup_pipe
1750 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001751 # First write the data length as an unsigned 4-byte value. This
1752 # is _not_ using network byte ordering since the other end of the
1753 # pipe is on the same machine.
1754 startup_pipe.write(struct.pack('=L', server_data_len))
1755 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001756 startup_pipe.close()
1757
initial.commit94958cf2008-07-26 22:42:52 +00001758 try:
1759 server.serve_forever()
1760 except KeyboardInterrupt:
1761 print 'shutting down server'
1762 server.stop = True
1763
1764if __name__ == '__main__':
1765 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001766 option_parser.add_option("-f", '--ftp', action='store_const',
1767 const=SERVER_FTP, default=SERVER_HTTP,
1768 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001769 help='start up an FTP server.')
1770 option_parser.add_option('', '--sync', action='store_const',
1771 const=SERVER_SYNC, default=SERVER_HTTP,
1772 dest='server_type',
1773 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001774 option_parser.add_option('', '--tcp-echo', action='store_const',
1775 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1776 dest='server_type',
1777 help='start up a tcp echo server.')
1778 option_parser.add_option('', '--udp-echo', action='store_const',
1779 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1780 dest='server_type',
1781 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001782 option_parser.add_option('', '--log-to-console', action='store_const',
1783 const=True, default=False,
1784 dest='log_to_console',
1785 help='Enables or disables sys.stdout logging to '
1786 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001787 option_parser.add_option('', '--port', default='0', type='int',
1788 help='Port used by the server. If unspecified, the '
1789 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001790 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001791 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001792 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001793 help='Specify that https should be used, specify '
1794 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001795 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001796 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1797 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001798 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1799 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001800 'should include the CA named in the subject of '
1801 'the DER-encoded certificate contained in the '
1802 'specified file. This option may appear multiple '
1803 'times, indicating multiple CA names should be '
1804 'sent in the request.')
1805 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1806 help='Specify the bulk encryption algorithm(s)'
1807 'that will be accepted by the SSL server. Valid '
1808 'values are "aes256", "aes128", "3des", "rc4". If '
1809 'omitted, all algorithms will be used. This '
1810 'option may appear multiple times, indicating '
1811 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001812 option_parser.add_option('', '--file-root-url', default='/files/',
1813 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001814 option_parser.add_option('', '--startup-pipe', type='int',
1815 dest='startup_pipe',
1816 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001817 option_parser.add_option('', '--policy-key', action='append',
1818 dest='policy_keys',
1819 help='Specify a path to a PEM-encoded private key '
1820 'to use for policy signing. May be specified '
1821 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001822 'the server. If ther server has multiple keys, it '
1823 'will rotate through them in at each request a '
1824 'round-robin fashion. The server will generate a '
1825 'random key if none is specified on the command '
1826 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001827 option_parser.add_option('', '--policy-user', default='user@example.com',
1828 dest='policy_user',
1829 help='Specify the user name the server should '
1830 'report back to the client as the user owning the '
1831 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001832 options, args = option_parser.parse_args()
1833
1834 sys.exit(main(options, args))