blob: 7f9e17a54129759cbf0409e9b16360503f570ef5 [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]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000135 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000136
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000137 def GetXmppServer(self):
138 return self._xmpp_server
139
akalin@chromium.org154bb132010-11-12 02:20:27 +0000140 def HandleCommand(self, query, raw_request):
141 return self._sync_handler.HandleCommand(query, raw_request)
142
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000143 def HandleRequestNoBlock(self):
144 """Handles a single request.
145
146 Copied from SocketServer._handle_request_noblock().
147 """
148 try:
149 request, client_address = self.get_request()
150 except socket.error:
151 return
152 if self.verify_request(request, client_address):
153 try:
154 self.process_request(request, client_address)
155 except:
156 self.handle_error(request, client_address)
157 self.close_request(request)
158
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000159 def SetAuthenticated(self, auth_valid):
160 self.authenticated = auth_valid
161
162 def GetAuthenticated(self):
163 return self.authenticated
164
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000165 def serve_forever(self):
166 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
167 """
168
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000169 def HandleXmppSocket(fd, socket_map, handler):
170 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000171
172 Adapted from asyncore.read() et al.
173 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000174 xmpp_connection = socket_map.get(fd)
175 # This could happen if a previous handler call caused fd to get
176 # removed from socket_map.
177 if xmpp_connection is None:
178 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000179 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000180 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000181 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
182 raise
183 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000184 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000185
186 while True:
187 read_fds = [ self.fileno() ]
188 write_fds = []
189 exceptional_fds = []
190
191 for fd, xmpp_connection in self._xmpp_socket_map.items():
192 is_r = xmpp_connection.readable()
193 is_w = xmpp_connection.writable()
194 if is_r:
195 read_fds.append(fd)
196 if is_w:
197 write_fds.append(fd)
198 if is_r or is_w:
199 exceptional_fds.append(fd)
200
201 try:
202 read_fds, write_fds, exceptional_fds = (
203 select.select(read_fds, write_fds, exceptional_fds))
204 except select.error, err:
205 if err.args[0] != errno.EINTR:
206 raise
207 else:
208 continue
209
210 for fd in read_fds:
211 if fd == self.fileno():
212 self.HandleRequestNoBlock()
213 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000214 HandleXmppSocket(fd, self._xmpp_socket_map,
215 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000216
217 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000218 HandleXmppSocket(fd, self._xmpp_socket_map,
219 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000220
221 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000222 HandleXmppSocket(fd, self._xmpp_socket_map,
223 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000224
akalin@chromium.org154bb132010-11-12 02:20:27 +0000225
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000226class TCPEchoServer(SocketServer.TCPServer):
227 """A TCP echo server that echoes back what it has received."""
228
229 def server_bind(self):
230 """Override server_bind to store the server name."""
231 SocketServer.TCPServer.server_bind(self)
232 host, port = self.socket.getsockname()[:2]
233 self.server_name = socket.getfqdn(host)
234 self.server_port = port
235
236 def serve_forever(self):
237 self.stop = False
238 self.nonce_time = None
239 while not self.stop:
240 self.handle_request()
241 self.socket.close()
242
243
244class UDPEchoServer(SocketServer.UDPServer):
245 """A UDP echo server that echoes back what it has received."""
246
247 def server_bind(self):
248 """Override server_bind to store the server name."""
249 SocketServer.UDPServer.server_bind(self)
250 host, port = self.socket.getsockname()[:2]
251 self.server_name = socket.getfqdn(host)
252 self.server_port = port
253
254 def serve_forever(self):
255 self.stop = False
256 self.nonce_time = None
257 while not self.stop:
258 self.handle_request()
259 self.socket.close()
260
261
akalin@chromium.org154bb132010-11-12 02:20:27 +0000262class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
263
264 def __init__(self, request, client_address, socket_server,
265 connect_handlers, get_handlers, post_handlers, put_handlers):
266 self._connect_handlers = connect_handlers
267 self._get_handlers = get_handlers
268 self._post_handlers = post_handlers
269 self._put_handlers = put_handlers
270 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
271 self, request, client_address, socket_server)
272
273 def log_request(self, *args, **kwargs):
274 # Disable request logging to declutter test log output.
275 pass
276
277 def _ShouldHandleRequest(self, handler_name):
278 """Determines if the path can be handled by the handler.
279
280 We consider a handler valid if the path begins with the
281 handler name. It can optionally be followed by "?*", "/*".
282 """
283
284 pattern = re.compile('%s($|\?|/).*' % handler_name)
285 return pattern.match(self.path)
286
287 def do_CONNECT(self):
288 for handler in self._connect_handlers:
289 if handler():
290 return
291
292 def do_GET(self):
293 for handler in self._get_handlers:
294 if handler():
295 return
296
297 def do_POST(self):
298 for handler in self._post_handlers:
299 if handler():
300 return
301
302 def do_PUT(self):
303 for handler in self._put_handlers:
304 if handler():
305 return
306
307
308class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000309
310 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000311 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000312 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000313 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000314 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000315 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000316 self.NoCacheMaxAgeTimeHandler,
317 self.NoCacheTimeHandler,
318 self.CacheTimeHandler,
319 self.CacheExpiresHandler,
320 self.CacheProxyRevalidateHandler,
321 self.CachePrivateHandler,
322 self.CachePublicHandler,
323 self.CacheSMaxAgeHandler,
324 self.CacheMustRevalidateHandler,
325 self.CacheMustRevalidateMaxAgeHandler,
326 self.CacheNoStoreHandler,
327 self.CacheNoStoreMaxAgeHandler,
328 self.CacheNoTransformHandler,
329 self.DownloadHandler,
330 self.DownloadFinishHandler,
331 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000332 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000333 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000334 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000336 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000337 self.AuthBasicHandler,
338 self.AuthDigestHandler,
339 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000340 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000341 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000342 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000343 self.ServerRedirectHandler,
344 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000345 self.MultipartHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000346 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000347 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000348 self.EchoTitleHandler,
349 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000350 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000351 self.DeviceManagementHandler] + get_handlers
352 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000353 self.EchoTitleHandler,
354 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000355 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000356
maruel@google.come250a9b2009-03-10 17:39:46 +0000357 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000358 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000359 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000360 'gif': 'image/gif',
361 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000362 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000363 'pdf' : 'application/pdf',
364 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 }
initial.commit94958cf2008-07-26 22:42:52 +0000366 self._default_mime_type = 'text/html'
367
akalin@chromium.org154bb132010-11-12 02:20:27 +0000368 BasePageHandler.__init__(self, request, client_address, socket_server,
369 connect_handlers, get_handlers, post_handlers,
370 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000371
initial.commit94958cf2008-07-26 22:42:52 +0000372 def GetMIMETypeFromName(self, file_name):
373 """Returns the mime type for the specified file_name. So far it only looks
374 at the file extension."""
375
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000376 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000377 if len(extension) == 0:
378 # no extension.
379 return self._default_mime_type
380
ericroman@google.comc17ca532009-05-07 03:51:05 +0000381 # extension starts with a dot, so we need to remove it
382 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000383
initial.commit94958cf2008-07-26 22:42:52 +0000384 def NoCacheMaxAgeTimeHandler(self):
385 """This request handler yields a page with the title set to the current
386 system time, and no caching requested."""
387
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000388 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000389 return False
390
391 self.send_response(200)
392 self.send_header('Cache-Control', 'max-age=0')
393 self.send_header('Content-type', 'text/html')
394 self.end_headers()
395
maruel@google.come250a9b2009-03-10 17:39:46 +0000396 self.wfile.write('<html><head><title>%s</title></head></html>' %
397 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000398
399 return True
400
401 def NoCacheTimeHandler(self):
402 """This request handler yields a page with the title set to the current
403 system time, and no caching requested."""
404
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000405 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000406 return False
407
408 self.send_response(200)
409 self.send_header('Cache-Control', 'no-cache')
410 self.send_header('Content-type', 'text/html')
411 self.end_headers()
412
maruel@google.come250a9b2009-03-10 17:39:46 +0000413 self.wfile.write('<html><head><title>%s</title></head></html>' %
414 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000415
416 return True
417
418 def CacheTimeHandler(self):
419 """This request handler yields a page with the title set to the current
420 system time, and allows caching for one minute."""
421
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000422 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000423 return False
424
425 self.send_response(200)
426 self.send_header('Cache-Control', 'max-age=60')
427 self.send_header('Content-type', 'text/html')
428 self.end_headers()
429
maruel@google.come250a9b2009-03-10 17:39:46 +0000430 self.wfile.write('<html><head><title>%s</title></head></html>' %
431 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000432
433 return True
434
435 def CacheExpiresHandler(self):
436 """This request handler yields a page with the title set to the current
437 system time, and set the page to expire on 1 Jan 2099."""
438
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000439 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000440 return False
441
442 self.send_response(200)
443 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
444 self.send_header('Content-type', 'text/html')
445 self.end_headers()
446
maruel@google.come250a9b2009-03-10 17:39:46 +0000447 self.wfile.write('<html><head><title>%s</title></head></html>' %
448 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000449
450 return True
451
452 def CacheProxyRevalidateHandler(self):
453 """This request handler yields a page with the title set to the current
454 system time, and allows caching for 60 seconds"""
455
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000456 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000457 return False
458
459 self.send_response(200)
460 self.send_header('Content-type', 'text/html')
461 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
462 self.end_headers()
463
maruel@google.come250a9b2009-03-10 17:39:46 +0000464 self.wfile.write('<html><head><title>%s</title></head></html>' %
465 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000466
467 return True
468
469 def CachePrivateHandler(self):
470 """This request handler yields a page with the title set to the current
471 system time, and allows caching for 5 seconds."""
472
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000473 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000474 return False
475
476 self.send_response(200)
477 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000478 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000479 self.end_headers()
480
maruel@google.come250a9b2009-03-10 17:39:46 +0000481 self.wfile.write('<html><head><title>%s</title></head></html>' %
482 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000483
484 return True
485
486 def CachePublicHandler(self):
487 """This request handler yields a page with the title set to the current
488 system time, and allows caching for 5 seconds."""
489
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000490 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000491 return False
492
493 self.send_response(200)
494 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000495 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000496 self.end_headers()
497
maruel@google.come250a9b2009-03-10 17:39:46 +0000498 self.wfile.write('<html><head><title>%s</title></head></html>' %
499 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000500
501 return True
502
503 def CacheSMaxAgeHandler(self):
504 """This request handler yields a page with the title set to the current
505 system time, and does not allow for caching."""
506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
511 self.send_header('Content-type', 'text/html')
512 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
513 self.end_headers()
514
maruel@google.come250a9b2009-03-10 17:39:46 +0000515 self.wfile.write('<html><head><title>%s</title></head></html>' %
516 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000517
518 return True
519
520 def CacheMustRevalidateHandler(self):
521 """This request handler yields a page with the title set to the current
522 system time, and does not allow caching."""
523
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000524 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000525 return False
526
527 self.send_response(200)
528 self.send_header('Content-type', 'text/html')
529 self.send_header('Cache-Control', 'must-revalidate')
530 self.end_headers()
531
maruel@google.come250a9b2009-03-10 17:39:46 +0000532 self.wfile.write('<html><head><title>%s</title></head></html>' %
533 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000534
535 return True
536
537 def CacheMustRevalidateMaxAgeHandler(self):
538 """This request handler yields a page with the title set to the current
539 system time, and does not allow caching event though max-age of 60
540 seconds is specified."""
541
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000542 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000543 return False
544
545 self.send_response(200)
546 self.send_header('Content-type', 'text/html')
547 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
548 self.end_headers()
549
maruel@google.come250a9b2009-03-10 17:39:46 +0000550 self.wfile.write('<html><head><title>%s</title></head></html>' %
551 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000552
553 return True
554
initial.commit94958cf2008-07-26 22:42:52 +0000555 def CacheNoStoreHandler(self):
556 """This request handler yields a page with the title set to the current
557 system time, and does not allow the page to be stored."""
558
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000559 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000560 return False
561
562 self.send_response(200)
563 self.send_header('Content-type', 'text/html')
564 self.send_header('Cache-Control', 'no-store')
565 self.end_headers()
566
maruel@google.come250a9b2009-03-10 17:39:46 +0000567 self.wfile.write('<html><head><title>%s</title></head></html>' %
568 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000569
570 return True
571
572 def CacheNoStoreMaxAgeHandler(self):
573 """This request handler yields a page with the title set to the current
574 system time, and does not allow the page to be stored even though max-age
575 of 60 seconds is specified."""
576
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000577 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000578 return False
579
580 self.send_response(200)
581 self.send_header('Content-type', 'text/html')
582 self.send_header('Cache-Control', 'max-age=60, no-store')
583 self.end_headers()
584
maruel@google.come250a9b2009-03-10 17:39:46 +0000585 self.wfile.write('<html><head><title>%s</title></head></html>' %
586 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000587
588 return True
589
590
591 def CacheNoTransformHandler(self):
592 """This request handler yields a page with the title set to the current
593 system time, and does not allow the content to transformed during
594 user-agent caching"""
595
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000596 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000597 return False
598
599 self.send_response(200)
600 self.send_header('Content-type', 'text/html')
601 self.send_header('Cache-Control', 'no-transform')
602 self.end_headers()
603
maruel@google.come250a9b2009-03-10 17:39:46 +0000604 self.wfile.write('<html><head><title>%s</title></head></html>' %
605 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000606
607 return True
608
609 def EchoHeader(self):
610 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000611 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000612
ananta@chromium.org56812d02011-04-07 17:52:05 +0000613 """This function echoes back the value of a specific request header"""
614 """while allowing caching for 16 hours."""
615 def EchoHeaderCache(self):
616 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000617
618 def EchoHeaderHelper(self, echo_header):
619 """This function echoes back the value of the request header passed in."""
620 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000621 return False
622
623 query_char = self.path.find('?')
624 if query_char != -1:
625 header_name = self.path[query_char+1:]
626
627 self.send_response(200)
628 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000629 if echo_header == '/echoheadercache':
630 self.send_header('Cache-control', 'max-age=60000')
631 else:
632 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000633 # insert a vary header to properly indicate that the cachability of this
634 # request is subject to value of the request header being echoed.
635 if len(header_name) > 0:
636 self.send_header('Vary', header_name)
637 self.end_headers()
638
639 if len(header_name) > 0:
640 self.wfile.write(self.headers.getheader(header_name))
641
642 return True
643
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000644 def ReadRequestBody(self):
645 """This function reads the body of the current HTTP request, handling
646 both plain and chunked transfer encoded requests."""
647
648 if self.headers.getheader('transfer-encoding') != 'chunked':
649 length = int(self.headers.getheader('content-length'))
650 return self.rfile.read(length)
651
652 # Read the request body as chunks.
653 body = ""
654 while True:
655 line = self.rfile.readline()
656 length = int(line, 16)
657 if length == 0:
658 self.rfile.readline()
659 break
660 body += self.rfile.read(length)
661 self.rfile.read(2)
662 return body
663
initial.commit94958cf2008-07-26 22:42:52 +0000664 def EchoHandler(self):
665 """This handler just echoes back the payload of the request, for testing
666 form submission."""
667
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000668 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000669 return False
670
671 self.send_response(200)
672 self.send_header('Content-type', 'text/html')
673 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000674 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000675 return True
676
677 def EchoTitleHandler(self):
678 """This handler is like Echo, but sets the page title to the request."""
679
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000680 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000681 return False
682
683 self.send_response(200)
684 self.send_header('Content-type', 'text/html')
685 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000686 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000687 self.wfile.write('<html><head><title>')
688 self.wfile.write(request)
689 self.wfile.write('</title></head></html>')
690 return True
691
692 def EchoAllHandler(self):
693 """This handler yields a (more) human-readable page listing information
694 about the request header & contents."""
695
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000696 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000697 return False
698
699 self.send_response(200)
700 self.send_header('Content-type', 'text/html')
701 self.end_headers()
702 self.wfile.write('<html><head><style>'
703 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
704 '</style></head><body>'
705 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000706 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000707 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000708
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000709 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000710 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000711 params = cgi.parse_qs(qs, keep_blank_values=1)
712
713 for param in params:
714 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000715
716 self.wfile.write('</pre>')
717
718 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
719
720 self.wfile.write('</body></html>')
721 return True
722
723 def DownloadHandler(self):
724 """This handler sends a downloadable file with or without reporting
725 the size (6K)."""
726
727 if self.path.startswith("/download-unknown-size"):
728 send_length = False
729 elif self.path.startswith("/download-known-size"):
730 send_length = True
731 else:
732 return False
733
734 #
735 # The test which uses this functionality is attempting to send
736 # small chunks of data to the client. Use a fairly large buffer
737 # so that we'll fill chrome's IO buffer enough to force it to
738 # actually write the data.
739 # See also the comments in the client-side of this test in
740 # download_uitest.cc
741 #
742 size_chunk1 = 35*1024
743 size_chunk2 = 10*1024
744
745 self.send_response(200)
746 self.send_header('Content-type', 'application/octet-stream')
747 self.send_header('Cache-Control', 'max-age=0')
748 if send_length:
749 self.send_header('Content-Length', size_chunk1 + size_chunk2)
750 self.end_headers()
751
752 # First chunk of data:
753 self.wfile.write("*" * size_chunk1)
754 self.wfile.flush()
755
756 # handle requests until one of them clears this flag.
757 self.server.waitForDownload = True
758 while self.server.waitForDownload:
759 self.server.handle_request()
760
761 # Second chunk of data:
762 self.wfile.write("*" * size_chunk2)
763 return True
764
765 def DownloadFinishHandler(self):
766 """This handler just tells the server to finish the current download."""
767
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000768 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000769 return False
770
771 self.server.waitForDownload = False
772 self.send_response(200)
773 self.send_header('Content-type', 'text/html')
774 self.send_header('Cache-Control', 'max-age=0')
775 self.end_headers()
776 return True
777
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000778 def _ReplaceFileData(self, data, query_parameters):
779 """Replaces matching substrings in a file.
780
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000781 If the 'replace_text' URL query parameter is present, it is expected to be
782 of the form old_text:new_text, which indicates that any old_text strings in
783 the file are replaced with new_text. Multiple 'replace_text' parameters may
784 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000785
786 If the parameters are not present, |data| is returned.
787 """
788 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000789 replace_text_values = query_dict.get('replace_text', [])
790 for replace_text_value in replace_text_values:
791 replace_text_args = replace_text_value.split(':')
792 if len(replace_text_args) != 2:
793 raise ValueError(
794 'replace_text must be of form old_text:new_text. Actual value: %s' %
795 replace_text_value)
796 old_text_b64, new_text_b64 = replace_text_args
797 old_text = base64.urlsafe_b64decode(old_text_b64)
798 new_text = base64.urlsafe_b64decode(new_text_b64)
799 data = data.replace(old_text, new_text)
800 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000801
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000802 def ZipFileHandler(self):
803 """This handler sends the contents of the requested file in compressed form.
804 Can pass in a parameter that specifies that the content length be
805 C - the compressed size (OK),
806 U - the uncompressed size (Non-standard, but handled),
807 S - less than compressed (OK because we keep going),
808 M - larger than compressed but less than uncompressed (an error),
809 L - larger than uncompressed (an error)
810 Example: compressedfiles/Picture_1.doc?C
811 """
812
813 prefix = "/compressedfiles/"
814 if not self.path.startswith(prefix):
815 return False
816
817 # Consume a request body if present.
818 if self.command == 'POST' or self.command == 'PUT' :
819 self.ReadRequestBody()
820
821 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
822
823 if not query in ('C', 'U', 'S', 'M', 'L'):
824 return False
825
826 sub_path = url_path[len(prefix):]
827 entries = sub_path.split('/')
828 file_path = os.path.join(self.server.data_dir, *entries)
829 if os.path.isdir(file_path):
830 file_path = os.path.join(file_path, 'index.html')
831
832 if not os.path.isfile(file_path):
833 print "File not found " + sub_path + " full path:" + file_path
834 self.send_error(404)
835 return True
836
837 f = open(file_path, "rb")
838 data = f.read()
839 uncompressed_len = len(data)
840 f.close()
841
842 # Compress the data.
843 data = zlib.compress(data)
844 compressed_len = len(data)
845
846 content_length = compressed_len
847 if query == 'U':
848 content_length = uncompressed_len
849 elif query == 'S':
850 content_length = compressed_len / 2
851 elif query == 'M':
852 content_length = (compressed_len + uncompressed_len) / 2
853 elif query == 'L':
854 content_length = compressed_len + uncompressed_len
855
856 self.send_response(200)
857 self.send_header('Content-type', 'application/msword')
858 self.send_header('Content-encoding', 'deflate')
859 self.send_header('Connection', 'close')
860 self.send_header('Content-Length', content_length)
861 self.send_header('ETag', '\'' + file_path + '\'')
862 self.end_headers()
863
864 self.wfile.write(data)
865
866 return True
867
initial.commit94958cf2008-07-26 22:42:52 +0000868 def FileHandler(self):
869 """This handler sends the contents of the requested file. Wow, it's like
870 a real webserver!"""
871
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000872 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000873 if not self.path.startswith(prefix):
874 return False
875
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000876 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000877 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000878 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000879
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000880 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
881 sub_path = url_path[len(prefix):]
882 entries = sub_path.split('/')
883 file_path = os.path.join(self.server.data_dir, *entries)
884 if os.path.isdir(file_path):
885 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000886
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000887 if not os.path.isfile(file_path):
888 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000889 self.send_error(404)
890 return True
891
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000892 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000893 data = f.read()
894 f.close()
895
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000896 data = self._ReplaceFileData(data, query)
897
initial.commit94958cf2008-07-26 22:42:52 +0000898 # If file.mock-http-headers exists, it contains the headers we
899 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000900 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000901 if os.path.isfile(headers_path):
902 f = open(headers_path, "r")
903
904 # "HTTP/1.1 200 OK"
905 response = f.readline()
906 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
907 self.send_response(int(status_code))
908
909 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000910 header_values = re.findall('(\S+):\s*(.*)', line)
911 if len(header_values) > 0:
912 # "name: value"
913 name, value = header_values[0]
914 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000915 f.close()
916 else:
917 # Could be more generic once we support mime-type sniffing, but for
918 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000919
920 range = self.headers.get('Range')
921 if range and range.startswith('bytes='):
922 # Note this doesn't handle all valid byte range values (i.e. open ended
923 # ones), just enough for what we needed so far.
924 range = range[6:].split('-')
925 start = int(range[0])
926 end = int(range[1])
927
928 self.send_response(206)
929 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
930 str(len(data))
931 self.send_header('Content-Range', content_range)
932 data = data[start: end + 1]
933 else:
934 self.send_response(200)
935
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000936 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000937 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000938 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000939 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000940 self.end_headers()
941
942 self.wfile.write(data)
943
944 return True
945
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000946 def SetCookieHandler(self):
947 """This handler just sets a cookie, for testing cookie handling."""
948
949 if not self._ShouldHandleRequest("/set-cookie"):
950 return False
951
952 query_char = self.path.find('?')
953 if query_char != -1:
954 cookie_values = self.path[query_char + 1:].split('&')
955 else:
956 cookie_values = ("",)
957 self.send_response(200)
958 self.send_header('Content-type', 'text/html')
959 for cookie_value in cookie_values:
960 self.send_header('Set-Cookie', '%s' % cookie_value)
961 self.end_headers()
962 for cookie_value in cookie_values:
963 self.wfile.write('%s' % cookie_value)
964 return True
965
initial.commit94958cf2008-07-26 22:42:52 +0000966 def AuthBasicHandler(self):
967 """This handler tests 'Basic' authentication. It just sends a page with
968 title 'user/pass' if you succeed."""
969
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000970 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000971 return False
972
973 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000974 expected_password = 'secret'
975 realm = 'testrealm'
976 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000977
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000978 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
979 query_params = cgi.parse_qs(query, True)
980 if 'set-cookie-if-challenged' in query_params:
981 set_cookie_if_challenged = True
982 if 'password' in query_params:
983 expected_password = query_params['password'][0]
984 if 'realm' in query_params:
985 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000986
initial.commit94958cf2008-07-26 22:42:52 +0000987 auth = self.headers.getheader('authorization')
988 try:
989 if not auth:
990 raise Exception('no auth')
991 b64str = re.findall(r'Basic (\S+)', auth)[0]
992 userpass = base64.b64decode(b64str)
993 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000994 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000995 raise Exception('wrong password')
996 except Exception, e:
997 # Authentication failed.
998 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000999 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +00001000 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001001 if set_cookie_if_challenged:
1002 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001003 self.end_headers()
1004 self.wfile.write('<html><head>')
1005 self.wfile.write('<title>Denied: %s</title>' % e)
1006 self.wfile.write('</head><body>')
1007 self.wfile.write('auth=%s<p>' % auth)
1008 self.wfile.write('b64str=%s<p>' % b64str)
1009 self.wfile.write('username: %s<p>' % username)
1010 self.wfile.write('userpass: %s<p>' % userpass)
1011 self.wfile.write('password: %s<p>' % password)
1012 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1013 self.wfile.write('</body></html>')
1014 return True
1015
1016 # Authentication successful. (Return a cachable response to allow for
1017 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001018 old_protocol_version = self.protocol_version
1019 self.protocol_version = "HTTP/1.1"
1020
initial.commit94958cf2008-07-26 22:42:52 +00001021 if_none_match = self.headers.getheader('if-none-match')
1022 if if_none_match == "abc":
1023 self.send_response(304)
1024 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001025 elif url_path.endswith(".gif"):
1026 # Using chrome/test/data/google/logo.gif as the test image
1027 test_image_path = ['google', 'logo.gif']
1028 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1029 if not os.path.isfile(gif_path):
1030 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001031 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001032 return True
1033
1034 f = open(gif_path, "rb")
1035 data = f.read()
1036 f.close()
1037
1038 self.send_response(200)
1039 self.send_header('Content-type', 'image/gif')
1040 self.send_header('Cache-control', 'max-age=60000')
1041 self.send_header('Etag', 'abc')
1042 self.end_headers()
1043 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001044 else:
1045 self.send_response(200)
1046 self.send_header('Content-type', 'text/html')
1047 self.send_header('Cache-control', 'max-age=60000')
1048 self.send_header('Etag', 'abc')
1049 self.end_headers()
1050 self.wfile.write('<html><head>')
1051 self.wfile.write('<title>%s/%s</title>' % (username, password))
1052 self.wfile.write('</head><body>')
1053 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001054 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001055 self.wfile.write('</body></html>')
1056
rvargas@google.com54453b72011-05-19 01:11:11 +00001057 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001058 return True
1059
tonyg@chromium.org75054202010-03-31 22:06:10 +00001060 def GetNonce(self, force_reset=False):
1061 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001062
tonyg@chromium.org75054202010-03-31 22:06:10 +00001063 This is a fake implementation. A real implementation would only use a given
1064 nonce a single time (hence the name n-once). However, for the purposes of
1065 unittesting, we don't care about the security of the nonce.
1066
1067 Args:
1068 force_reset: Iff set, the nonce will be changed. Useful for testing the
1069 "stale" response.
1070 """
1071 if force_reset or not self.server.nonce_time:
1072 self.server.nonce_time = time.time()
1073 return _new_md5('privatekey%s%d' %
1074 (self.path, self.server.nonce_time)).hexdigest()
1075
1076 def AuthDigestHandler(self):
1077 """This handler tests 'Digest' authentication.
1078
1079 It just sends a page with title 'user/pass' if you succeed.
1080
1081 A stale response is sent iff "stale" is present in the request path.
1082 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001083 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001084 return False
1085
tonyg@chromium.org75054202010-03-31 22:06:10 +00001086 stale = 'stale' in self.path
1087 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001088 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001089 password = 'secret'
1090 realm = 'testrealm'
1091
1092 auth = self.headers.getheader('authorization')
1093 pairs = {}
1094 try:
1095 if not auth:
1096 raise Exception('no auth')
1097 if not auth.startswith('Digest'):
1098 raise Exception('not digest')
1099 # Pull out all the name="value" pairs as a dictionary.
1100 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1101
1102 # Make sure it's all valid.
1103 if pairs['nonce'] != nonce:
1104 raise Exception('wrong nonce')
1105 if pairs['opaque'] != opaque:
1106 raise Exception('wrong opaque')
1107
1108 # Check the 'response' value and make sure it matches our magic hash.
1109 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001110 hash_a1 = _new_md5(
1111 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001112 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001113 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001114 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001115 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1116 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001117 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001118
1119 if pairs['response'] != response:
1120 raise Exception('wrong password')
1121 except Exception, e:
1122 # Authentication failed.
1123 self.send_response(401)
1124 hdr = ('Digest '
1125 'realm="%s", '
1126 'domain="/", '
1127 'qop="auth", '
1128 'algorithm=MD5, '
1129 'nonce="%s", '
1130 'opaque="%s"') % (realm, nonce, opaque)
1131 if stale:
1132 hdr += ', stale="TRUE"'
1133 self.send_header('WWW-Authenticate', hdr)
1134 self.send_header('Content-type', 'text/html')
1135 self.end_headers()
1136 self.wfile.write('<html><head>')
1137 self.wfile.write('<title>Denied: %s</title>' % e)
1138 self.wfile.write('</head><body>')
1139 self.wfile.write('auth=%s<p>' % auth)
1140 self.wfile.write('pairs=%s<p>' % pairs)
1141 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1142 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1143 self.wfile.write('</body></html>')
1144 return True
1145
1146 # Authentication successful.
1147 self.send_response(200)
1148 self.send_header('Content-type', 'text/html')
1149 self.end_headers()
1150 self.wfile.write('<html><head>')
1151 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1152 self.wfile.write('</head><body>')
1153 self.wfile.write('auth=%s<p>' % auth)
1154 self.wfile.write('pairs=%s<p>' % pairs)
1155 self.wfile.write('</body></html>')
1156
1157 return True
1158
1159 def SlowServerHandler(self):
1160 """Wait for the user suggested time before responding. The syntax is
1161 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001162 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001163 return False
1164 query_char = self.path.find('?')
1165 wait_sec = 1.0
1166 if query_char >= 0:
1167 try:
1168 wait_sec = int(self.path[query_char + 1:])
1169 except ValueError:
1170 pass
1171 time.sleep(wait_sec)
1172 self.send_response(200)
1173 self.send_header('Content-type', 'text/plain')
1174 self.end_headers()
1175 self.wfile.write("waited %d seconds" % wait_sec)
1176 return True
1177
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001178 def ChunkedServerHandler(self):
1179 """Send chunked response. Allows to specify chunks parameters:
1180 - waitBeforeHeaders - ms to wait before sending headers
1181 - waitBetweenChunks - ms to wait between chunks
1182 - chunkSize - size of each chunk in bytes
1183 - chunksNumber - number of chunks
1184 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1185 waits one second, then sends headers and five chunks five bytes each."""
1186 if not self._ShouldHandleRequest("/chunked"):
1187 return False
1188 query_char = self.path.find('?')
1189 chunkedSettings = {'waitBeforeHeaders' : 0,
1190 'waitBetweenChunks' : 0,
1191 'chunkSize' : 5,
1192 'chunksNumber' : 5}
1193 if query_char >= 0:
1194 params = self.path[query_char + 1:].split('&')
1195 for param in params:
1196 keyValue = param.split('=')
1197 if len(keyValue) == 2:
1198 try:
1199 chunkedSettings[keyValue[0]] = int(keyValue[1])
1200 except ValueError:
1201 pass
1202 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1203 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1204 self.send_response(200)
1205 self.send_header('Content-type', 'text/plain')
1206 self.send_header('Connection', 'close')
1207 self.send_header('Transfer-Encoding', 'chunked')
1208 self.end_headers()
1209 # Chunked encoding: sending all chunks, then final zero-length chunk and
1210 # then final CRLF.
1211 for i in range(0, chunkedSettings['chunksNumber']):
1212 if i > 0:
1213 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1214 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1215 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1216 self.sendChunkHelp('')
1217 return True
1218
initial.commit94958cf2008-07-26 22:42:52 +00001219 def ContentTypeHandler(self):
1220 """Returns a string of html with the given content type. E.g.,
1221 /contenttype?text/css returns an html file with the Content-Type
1222 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001223 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001224 return False
1225 query_char = self.path.find('?')
1226 content_type = self.path[query_char + 1:].strip()
1227 if not content_type:
1228 content_type = 'text/html'
1229 self.send_response(200)
1230 self.send_header('Content-Type', content_type)
1231 self.end_headers()
1232 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1233 return True
1234
creis@google.com2f4f6a42011-03-25 19:44:19 +00001235 def NoContentHandler(self):
1236 """Returns a 204 No Content response."""
1237 if not self._ShouldHandleRequest("/nocontent"):
1238 return False
1239 self.send_response(204)
1240 self.end_headers()
1241 return True
1242
initial.commit94958cf2008-07-26 22:42:52 +00001243 def ServerRedirectHandler(self):
1244 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001245 '/server-redirect?http://foo.bar/asdf' to redirect to
1246 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001247
1248 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001249 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001250 return False
1251
1252 query_char = self.path.find('?')
1253 if query_char < 0 or len(self.path) <= query_char + 1:
1254 self.sendRedirectHelp(test_name)
1255 return True
1256 dest = self.path[query_char + 1:]
1257
1258 self.send_response(301) # moved permanently
1259 self.send_header('Location', dest)
1260 self.send_header('Content-type', 'text/html')
1261 self.end_headers()
1262 self.wfile.write('<html><head>')
1263 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1264
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001265 return True
initial.commit94958cf2008-07-26 22:42:52 +00001266
1267 def ClientRedirectHandler(self):
1268 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001269 '/client-redirect?http://foo.bar/asdf' to redirect to
1270 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001271
1272 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001273 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001274 return False
1275
1276 query_char = self.path.find('?');
1277 if query_char < 0 or len(self.path) <= query_char + 1:
1278 self.sendRedirectHelp(test_name)
1279 return True
1280 dest = self.path[query_char + 1:]
1281
1282 self.send_response(200)
1283 self.send_header('Content-type', 'text/html')
1284 self.end_headers()
1285 self.wfile.write('<html><head>')
1286 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1287 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1288
1289 return True
1290
tony@chromium.org03266982010-03-05 03:18:42 +00001291 def MultipartHandler(self):
1292 """Send a multipart response (10 text/html pages)."""
1293 test_name = "/multipart"
1294 if not self._ShouldHandleRequest(test_name):
1295 return False
1296
1297 num_frames = 10
1298 bound = '12345'
1299 self.send_response(200)
1300 self.send_header('Content-type',
1301 'multipart/x-mixed-replace;boundary=' + bound)
1302 self.end_headers()
1303
1304 for i in xrange(num_frames):
1305 self.wfile.write('--' + bound + '\r\n')
1306 self.wfile.write('Content-type: text/html\r\n\r\n')
1307 self.wfile.write('<title>page ' + str(i) + '</title>')
1308 self.wfile.write('page ' + str(i))
1309
1310 self.wfile.write('--' + bound + '--')
1311 return True
1312
initial.commit94958cf2008-07-26 22:42:52 +00001313 def DefaultResponseHandler(self):
1314 """This is the catch-all response handler for requests that aren't handled
1315 by one of the special handlers above.
1316 Note that we specify the content-length as without it the https connection
1317 is not closed properly (and the browser keeps expecting data)."""
1318
1319 contents = "Default response given for path: " + self.path
1320 self.send_response(200)
1321 self.send_header('Content-type', 'text/html')
1322 self.send_header("Content-Length", len(contents))
1323 self.end_headers()
1324 self.wfile.write(contents)
1325 return True
1326
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001327 def RedirectConnectHandler(self):
1328 """Sends a redirect to the CONNECT request for www.redirect.com. This
1329 response is not specified by the RFC, so the browser should not follow
1330 the redirect."""
1331
1332 if (self.path.find("www.redirect.com") < 0):
1333 return False
1334
1335 dest = "http://www.destination.com/foo.js"
1336
1337 self.send_response(302) # moved temporarily
1338 self.send_header('Location', dest)
1339 self.send_header('Connection', 'close')
1340 self.end_headers()
1341 return True
1342
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001343 def ServerAuthConnectHandler(self):
1344 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1345 response doesn't make sense because the proxy server cannot request
1346 server authentication."""
1347
1348 if (self.path.find("www.server-auth.com") < 0):
1349 return False
1350
1351 challenge = 'Basic realm="WallyWorld"'
1352
1353 self.send_response(401) # unauthorized
1354 self.send_header('WWW-Authenticate', challenge)
1355 self.send_header('Connection', 'close')
1356 self.end_headers()
1357 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001358
1359 def DefaultConnectResponseHandler(self):
1360 """This is the catch-all response handler for CONNECT requests that aren't
1361 handled by one of the special handlers above. Real Web servers respond
1362 with 400 to CONNECT requests."""
1363
1364 contents = "Your client has issued a malformed or illegal request."
1365 self.send_response(400) # bad request
1366 self.send_header('Content-type', 'text/html')
1367 self.send_header("Content-Length", len(contents))
1368 self.end_headers()
1369 self.wfile.write(contents)
1370 return True
1371
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001372 def DeviceManagementHandler(self):
1373 """Delegates to the device management service used for cloud policy."""
1374 if not self._ShouldHandleRequest("/device_management"):
1375 return False
1376
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001377 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001378
1379 if not self.server._device_management_handler:
1380 import device_management
1381 policy_path = os.path.join(self.server.data_dir, 'device_management')
1382 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001383 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001384 self.server.policy_keys,
1385 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001386
1387 http_response, raw_reply = (
1388 self.server._device_management_handler.HandleRequest(self.path,
1389 self.headers,
1390 raw_request))
1391 self.send_response(http_response)
1392 self.end_headers()
1393 self.wfile.write(raw_reply)
1394 return True
1395
initial.commit94958cf2008-07-26 22:42:52 +00001396 # called by the redirect handling function when there is no parameter
1397 def sendRedirectHelp(self, redirect_name):
1398 self.send_response(200)
1399 self.send_header('Content-type', 'text/html')
1400 self.end_headers()
1401 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1402 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1403 self.wfile.write('</body></html>')
1404
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001405 # called by chunked handling function
1406 def sendChunkHelp(self, chunk):
1407 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1408 self.wfile.write('%X\r\n' % len(chunk))
1409 self.wfile.write(chunk)
1410 self.wfile.write('\r\n')
1411
akalin@chromium.org154bb132010-11-12 02:20:27 +00001412
1413class SyncPageHandler(BasePageHandler):
1414 """Handler for the main HTTP sync server."""
1415
1416 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001417 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001418 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001419 self.ChromiumSyncDisableNotificationsOpHandler,
1420 self.ChromiumSyncEnableNotificationsOpHandler,
1421 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001422 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001423 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001424 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001425 self.ChromiumSyncErrorOpHandler,
1426 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001427
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001428 post_handlers = [self.ChromiumSyncCommandHandler,
1429 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001430 BasePageHandler.__init__(self, request, client_address,
1431 sync_http_server, [], get_handlers,
1432 post_handlers, [])
1433
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001434
akalin@chromium.org154bb132010-11-12 02:20:27 +00001435 def ChromiumSyncTimeHandler(self):
1436 """Handle Chromium sync .../time requests.
1437
1438 The syncer sometimes checks server reachability by examining /time.
1439 """
1440 test_name = "/chromiumsync/time"
1441 if not self._ShouldHandleRequest(test_name):
1442 return False
1443
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001444 # Chrome hates it if we send a response before reading the request.
1445 if self.headers.getheader('content-length'):
1446 length = int(self.headers.getheader('content-length'))
1447 raw_request = self.rfile.read(length)
1448
akalin@chromium.org154bb132010-11-12 02:20:27 +00001449 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001450 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001451 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001452 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001453 return True
1454
1455 def ChromiumSyncCommandHandler(self):
1456 """Handle a chromiumsync command arriving via http.
1457
1458 This covers all sync protocol commands: authentication, getupdates, and
1459 commit.
1460 """
1461 test_name = "/chromiumsync/command"
1462 if not self._ShouldHandleRequest(test_name):
1463 return False
1464
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001465 length = int(self.headers.getheader('content-length'))
1466 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001467 http_response = 200
1468 raw_reply = None
1469 if not self.server.GetAuthenticated():
1470 http_response = 401
1471 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1472 else:
1473 http_response, raw_reply = self.server.HandleCommand(
1474 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001475
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001476 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001477 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001478 if http_response == 401:
1479 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001480 self.end_headers()
1481 self.wfile.write(raw_reply)
1482 return True
1483
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001484 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001485 test_name = "/chromiumsync/migrate"
1486 if not self._ShouldHandleRequest(test_name):
1487 return False
1488
1489 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1490 self.path)
1491 self.send_response(http_response)
1492 self.send_header('Content-Type', 'text/html')
1493 self.send_header('Content-Length', len(raw_reply))
1494 self.end_headers()
1495 self.wfile.write(raw_reply)
1496 return True
1497
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001498 def ChromiumSyncCredHandler(self):
1499 test_name = "/chromiumsync/cred"
1500 if not self._ShouldHandleRequest(test_name):
1501 return False
1502 try:
1503 query = urlparse.urlparse(self.path)[4]
1504 cred_valid = urlparse.parse_qs(query)['valid']
1505 if cred_valid[0] == 'True':
1506 self.server.SetAuthenticated(True)
1507 else:
1508 self.server.SetAuthenticated(False)
1509 except:
1510 self.server.SetAuthenticated(False)
1511
1512 http_response = 200
1513 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1514 self.send_response(http_response)
1515 self.send_header('Content-Type', 'text/html')
1516 self.send_header('Content-Length', len(raw_reply))
1517 self.end_headers()
1518 self.wfile.write(raw_reply)
1519 return True
1520
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001521 def ChromiumSyncDisableNotificationsOpHandler(self):
1522 test_name = "/chromiumsync/disablenotifications"
1523 if not self._ShouldHandleRequest(test_name):
1524 return False
1525 self.server.GetXmppServer().DisableNotifications()
1526 result = 200
1527 raw_reply = ('<html><title>Notifications disabled</title>'
1528 '<H1>Notifications disabled</H1></html>')
1529 self.send_response(result)
1530 self.send_header('Content-Type', 'text/html')
1531 self.send_header('Content-Length', len(raw_reply))
1532 self.end_headers()
1533 self.wfile.write(raw_reply)
1534 return True;
1535
1536 def ChromiumSyncEnableNotificationsOpHandler(self):
1537 test_name = "/chromiumsync/enablenotifications"
1538 if not self._ShouldHandleRequest(test_name):
1539 return False
1540 self.server.GetXmppServer().EnableNotifications()
1541 result = 200
1542 raw_reply = ('<html><title>Notifications enabled</title>'
1543 '<H1>Notifications enabled</H1></html>')
1544 self.send_response(result)
1545 self.send_header('Content-Type', 'text/html')
1546 self.send_header('Content-Length', len(raw_reply))
1547 self.end_headers()
1548 self.wfile.write(raw_reply)
1549 return True;
1550
1551 def ChromiumSyncSendNotificationOpHandler(self):
1552 test_name = "/chromiumsync/sendnotification"
1553 if not self._ShouldHandleRequest(test_name):
1554 return False
1555 query = urlparse.urlparse(self.path)[4]
1556 query_params = urlparse.parse_qs(query)
1557 channel = ''
1558 data = ''
1559 if 'channel' in query_params:
1560 channel = query_params['channel'][0]
1561 if 'data' in query_params:
1562 data = query_params['data'][0]
1563 self.server.GetXmppServer().SendNotification(channel, data)
1564 result = 200
1565 raw_reply = ('<html><title>Notification sent</title>'
1566 '<H1>Notification sent with channel "%s" '
1567 'and data "%s"</H1></html>'
1568 % (channel, data))
1569 self.send_response(result)
1570 self.send_header('Content-Type', 'text/html')
1571 self.send_header('Content-Length', len(raw_reply))
1572 self.end_headers()
1573 self.wfile.write(raw_reply)
1574 return True;
1575
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001576 def ChromiumSyncBirthdayErrorOpHandler(self):
1577 test_name = "/chromiumsync/birthdayerror"
1578 if not self._ShouldHandleRequest(test_name):
1579 return False
1580 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1581 self.send_response(result)
1582 self.send_header('Content-Type', 'text/html')
1583 self.send_header('Content-Length', len(raw_reply))
1584 self.end_headers()
1585 self.wfile.write(raw_reply)
1586 return True;
1587
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001588 def ChromiumSyncTransientErrorOpHandler(self):
1589 test_name = "/chromiumsync/transienterror"
1590 if not self._ShouldHandleRequest(test_name):
1591 return False
1592 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1593 self.send_response(result)
1594 self.send_header('Content-Type', 'text/html')
1595 self.send_header('Content-Length', len(raw_reply))
1596 self.end_headers()
1597 self.wfile.write(raw_reply)
1598 return True;
1599
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001600 def ChromiumSyncErrorOpHandler(self):
1601 test_name = "/chromiumsync/error"
1602 if not self._ShouldHandleRequest(test_name):
1603 return False
1604 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1605 self.path)
1606 self.send_response(result)
1607 self.send_header('Content-Type', 'text/html')
1608 self.send_header('Content-Length', len(raw_reply))
1609 self.end_headers()
1610 self.wfile.write(raw_reply)
1611 return True;
1612
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001613 def ChromiumSyncSyncTabsOpHandler(self):
1614 test_name = "/chromiumsync/synctabs"
1615 if not self._ShouldHandleRequest(test_name):
1616 return False
1617 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1618 self.send_response(result)
1619 self.send_header('Content-Type', 'text/html')
1620 self.send_header('Content-Length', len(raw_reply))
1621 self.end_headers()
1622 self.wfile.write(raw_reply)
1623 return True;
1624
akalin@chromium.org154bb132010-11-12 02:20:27 +00001625
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001626def MakeDataDir():
1627 if options.data_dir:
1628 if not os.path.isdir(options.data_dir):
1629 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1630 return None
1631 my_data_dir = options.data_dir
1632 else:
1633 # Create the default path to our data dir, relative to the exe dir.
1634 my_data_dir = os.path.dirname(sys.argv[0])
1635 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001636 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001637
1638 #TODO(ibrar): Must use Find* funtion defined in google\tools
1639 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1640
1641 return my_data_dir
1642
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001643
1644class TCPEchoHandler(SocketServer.BaseRequestHandler):
1645 """The RequestHandler class for TCP echo server.
1646
1647 It is instantiated once per connection to the server, and overrides the
1648 handle() method to implement communication to the client.
1649 """
1650
1651 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001652 """Handles the request from the client and constructs a response."""
1653
1654 data = self.request.recv(65536).strip()
1655 # Verify the "echo request" message received from the client. Send back
1656 # "echo response" message if "echo request" message is valid.
1657 try:
1658 return_data = echo_message.GetEchoResponseData(data)
1659 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001660 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001661 except ValueError:
1662 return
1663
1664 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001665
1666
1667class UDPEchoHandler(SocketServer.BaseRequestHandler):
1668 """The RequestHandler class for UDP echo server.
1669
1670 It is instantiated once per connection to the server, and overrides the
1671 handle() method to implement communication to the client.
1672 """
1673
1674 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001675 """Handles the request from the client and constructs a response."""
1676
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001677 data = self.request[0].strip()
1678 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001679 # Verify the "echo request" message received from the client. Send back
1680 # "echo response" message if "echo request" message is valid.
1681 try:
1682 return_data = echo_message.GetEchoResponseData(data)
1683 if not return_data:
1684 return
1685 except ValueError:
1686 return
1687 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001688
1689
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001690class FileMultiplexer:
1691 def __init__(self, fd1, fd2) :
1692 self.__fd1 = fd1
1693 self.__fd2 = fd2
1694
1695 def __del__(self) :
1696 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1697 self.__fd1.close()
1698 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1699 self.__fd2.close()
1700
1701 def write(self, text) :
1702 self.__fd1.write(text)
1703 self.__fd2.write(text)
1704
1705 def flush(self) :
1706 self.__fd1.flush()
1707 self.__fd2.flush()
1708
initial.commit94958cf2008-07-26 22:42:52 +00001709def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001710 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001711 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001712 if options.log_to_console:
1713 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1714 else:
1715 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001716
1717 port = options.port
1718
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001719 server_data = {}
1720
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001721 if options.server_type == SERVER_HTTP:
1722 if options.cert:
1723 # let's make sure the cert file exists.
1724 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001725 print 'specified server cert file not found: ' + options.cert + \
1726 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001727 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001728 for ca_cert in options.ssl_client_ca:
1729 if not os.path.isfile(ca_cert):
1730 print 'specified trusted client CA file not found: ' + ca_cert + \
1731 ' exiting...'
1732 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001733 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001734 options.ssl_client_auth, options.ssl_client_ca,
1735 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001736 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001737 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001738 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001739 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001740
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001741 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001742 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001743 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001744 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001745 server.policy_keys = options.policy_keys
1746 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001747 elif options.server_type == SERVER_SYNC:
1748 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1749 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001750 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1751 server_data['port'] = server.server_port
1752 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001753 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001754 # Used for generating the key (randomly) that encodes the "echo request"
1755 # message.
1756 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001757 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1758 print 'Echo TCP server started on port %d...' % server.server_port
1759 server_data['port'] = server.server_port
1760 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001761 # Used for generating the key (randomly) that encodes the "echo request"
1762 # message.
1763 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001764 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1765 print 'Echo UDP server started on port %d...' % server.server_port
1766 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001767 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001768 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001769 my_data_dir = MakeDataDir()
1770
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001771 # Instantiate a dummy authorizer for managing 'virtual' users
1772 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1773
1774 # Define a new user having full r/w permissions and a read-only
1775 # anonymous user
1776 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1777
1778 authorizer.add_anonymous(my_data_dir)
1779
1780 # Instantiate FTP handler class
1781 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1782 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001783
1784 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001785 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1786 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001787
1788 # Instantiate FTP server class and listen to 127.0.0.1:port
1789 address = ('127.0.0.1', port)
1790 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001791 server_data['port'] = server.socket.getsockname()[1]
1792 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001793
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001794 # Notify the parent that we've started. (BaseServer subclasses
1795 # bind their sockets on construction.)
1796 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001797 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001798 server_data_len = len(server_data_json)
1799 print 'sending server_data: %s (%d bytes)' % (
1800 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001801 if sys.platform == 'win32':
1802 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1803 else:
1804 fd = options.startup_pipe
1805 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001806 # First write the data length as an unsigned 4-byte value. This
1807 # is _not_ using network byte ordering since the other end of the
1808 # pipe is on the same machine.
1809 startup_pipe.write(struct.pack('=L', server_data_len))
1810 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001811 startup_pipe.close()
1812
initial.commit94958cf2008-07-26 22:42:52 +00001813 try:
1814 server.serve_forever()
1815 except KeyboardInterrupt:
1816 print 'shutting down server'
1817 server.stop = True
1818
1819if __name__ == '__main__':
1820 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001821 option_parser.add_option("-f", '--ftp', action='store_const',
1822 const=SERVER_FTP, default=SERVER_HTTP,
1823 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001824 help='start up an FTP server.')
1825 option_parser.add_option('', '--sync', action='store_const',
1826 const=SERVER_SYNC, default=SERVER_HTTP,
1827 dest='server_type',
1828 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001829 option_parser.add_option('', '--tcp-echo', action='store_const',
1830 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1831 dest='server_type',
1832 help='start up a tcp echo server.')
1833 option_parser.add_option('', '--udp-echo', action='store_const',
1834 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1835 dest='server_type',
1836 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001837 option_parser.add_option('', '--log-to-console', action='store_const',
1838 const=True, default=False,
1839 dest='log_to_console',
1840 help='Enables or disables sys.stdout logging to '
1841 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001842 option_parser.add_option('', '--port', default='0', type='int',
1843 help='Port used by the server. If unspecified, the '
1844 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001845 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001846 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001847 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001848 help='Specify that https should be used, specify '
1849 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001850 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001851 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1852 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001853 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1854 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001855 'should include the CA named in the subject of '
1856 'the DER-encoded certificate contained in the '
1857 'specified file. This option may appear multiple '
1858 'times, indicating multiple CA names should be '
1859 'sent in the request.')
1860 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1861 help='Specify the bulk encryption algorithm(s)'
1862 'that will be accepted by the SSL server. Valid '
1863 'values are "aes256", "aes128", "3des", "rc4". If '
1864 'omitted, all algorithms will be used. This '
1865 'option may appear multiple times, indicating '
1866 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001867 option_parser.add_option('', '--file-root-url', default='/files/',
1868 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001869 option_parser.add_option('', '--startup-pipe', type='int',
1870 dest='startup_pipe',
1871 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001872 option_parser.add_option('', '--policy-key', action='append',
1873 dest='policy_keys',
1874 help='Specify a path to a PEM-encoded private key '
1875 'to use for policy signing. May be specified '
1876 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001877 'the server. If ther server has multiple keys, it '
1878 'will rotate through them in at each request a '
1879 'round-robin fashion. The server will generate a '
1880 'random key if none is specified on the command '
1881 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001882 option_parser.add_option('', '--policy-user', default='user@example.com',
1883 dest='policy_user',
1884 help='Specify the user name the server should '
1885 'report back to the client as the user owning the '
1886 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001887 options, args = option_parser.parse_args()
1888
1889 sys.exit(main(options, args))