blob: 0849a2917b58edb22a81c3a90641b90b7aac2e41 [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,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000346 self.MultipartSlowHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000347 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000348 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000349 self.EchoTitleHandler,
350 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000351 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352 self.DeviceManagementHandler] + get_handlers
353 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000354 self.EchoTitleHandler,
355 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000356 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000357
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000359 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000360 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 'gif': 'image/gif',
362 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000363 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000364 'pdf' : 'application/pdf',
365 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 }
initial.commit94958cf2008-07-26 22:42:52 +0000367 self._default_mime_type = 'text/html'
368
akalin@chromium.org154bb132010-11-12 02:20:27 +0000369 BasePageHandler.__init__(self, request, client_address, socket_server,
370 connect_handlers, get_handlers, post_handlers,
371 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000372
initial.commit94958cf2008-07-26 22:42:52 +0000373 def GetMIMETypeFromName(self, file_name):
374 """Returns the mime type for the specified file_name. So far it only looks
375 at the file extension."""
376
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000377 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000378 if len(extension) == 0:
379 # no extension.
380 return self._default_mime_type
381
ericroman@google.comc17ca532009-05-07 03:51:05 +0000382 # extension starts with a dot, so we need to remove it
383 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000384
initial.commit94958cf2008-07-26 22:42:52 +0000385 def NoCacheMaxAgeTimeHandler(self):
386 """This request handler yields a page with the title set to the current
387 system time, and no caching requested."""
388
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000389 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000390 return False
391
392 self.send_response(200)
393 self.send_header('Cache-Control', 'max-age=0')
394 self.send_header('Content-type', 'text/html')
395 self.end_headers()
396
maruel@google.come250a9b2009-03-10 17:39:46 +0000397 self.wfile.write('<html><head><title>%s</title></head></html>' %
398 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000399
400 return True
401
402 def NoCacheTimeHandler(self):
403 """This request handler yields a page with the title set to the current
404 system time, and no caching requested."""
405
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000406 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000407 return False
408
409 self.send_response(200)
410 self.send_header('Cache-Control', 'no-cache')
411 self.send_header('Content-type', 'text/html')
412 self.end_headers()
413
maruel@google.come250a9b2009-03-10 17:39:46 +0000414 self.wfile.write('<html><head><title>%s</title></head></html>' %
415 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000416
417 return True
418
419 def CacheTimeHandler(self):
420 """This request handler yields a page with the title set to the current
421 system time, and allows caching for one minute."""
422
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000423 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000424 return False
425
426 self.send_response(200)
427 self.send_header('Cache-Control', 'max-age=60')
428 self.send_header('Content-type', 'text/html')
429 self.end_headers()
430
maruel@google.come250a9b2009-03-10 17:39:46 +0000431 self.wfile.write('<html><head><title>%s</title></head></html>' %
432 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000433
434 return True
435
436 def CacheExpiresHandler(self):
437 """This request handler yields a page with the title set to the current
438 system time, and set the page to expire on 1 Jan 2099."""
439
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000440 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000441 return False
442
443 self.send_response(200)
444 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
445 self.send_header('Content-type', 'text/html')
446 self.end_headers()
447
maruel@google.come250a9b2009-03-10 17:39:46 +0000448 self.wfile.write('<html><head><title>%s</title></head></html>' %
449 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000450
451 return True
452
453 def CacheProxyRevalidateHandler(self):
454 """This request handler yields a page with the title set to the current
455 system time, and allows caching for 60 seconds"""
456
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000457 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000458 return False
459
460 self.send_response(200)
461 self.send_header('Content-type', 'text/html')
462 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
463 self.end_headers()
464
maruel@google.come250a9b2009-03-10 17:39:46 +0000465 self.wfile.write('<html><head><title>%s</title></head></html>' %
466 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000467
468 return True
469
470 def CachePrivateHandler(self):
471 """This request handler yields a page with the title set to the current
472 system time, and allows caching for 5 seconds."""
473
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000474 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000475 return False
476
477 self.send_response(200)
478 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000479 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000480 self.end_headers()
481
maruel@google.come250a9b2009-03-10 17:39:46 +0000482 self.wfile.write('<html><head><title>%s</title></head></html>' %
483 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000484
485 return True
486
487 def CachePublicHandler(self):
488 """This request handler yields a page with the title set to the current
489 system time, and allows caching for 5 seconds."""
490
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000491 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000492 return False
493
494 self.send_response(200)
495 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000496 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000497 self.end_headers()
498
maruel@google.come250a9b2009-03-10 17:39:46 +0000499 self.wfile.write('<html><head><title>%s</title></head></html>' %
500 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000501
502 return True
503
504 def CacheSMaxAgeHandler(self):
505 """This request handler yields a page with the title set to the current
506 system time, and does not allow for caching."""
507
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000508 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000509 return False
510
511 self.send_response(200)
512 self.send_header('Content-type', 'text/html')
513 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
514 self.end_headers()
515
maruel@google.come250a9b2009-03-10 17:39:46 +0000516 self.wfile.write('<html><head><title>%s</title></head></html>' %
517 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000518
519 return True
520
521 def CacheMustRevalidateHandler(self):
522 """This request handler yields a page with the title set to the current
523 system time, and does not allow caching."""
524
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000525 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000526 return False
527
528 self.send_response(200)
529 self.send_header('Content-type', 'text/html')
530 self.send_header('Cache-Control', 'must-revalidate')
531 self.end_headers()
532
maruel@google.come250a9b2009-03-10 17:39:46 +0000533 self.wfile.write('<html><head><title>%s</title></head></html>' %
534 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000535
536 return True
537
538 def CacheMustRevalidateMaxAgeHandler(self):
539 """This request handler yields a page with the title set to the current
540 system time, and does not allow caching event though max-age of 60
541 seconds is specified."""
542
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000543 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000544 return False
545
546 self.send_response(200)
547 self.send_header('Content-type', 'text/html')
548 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
549 self.end_headers()
550
maruel@google.come250a9b2009-03-10 17:39:46 +0000551 self.wfile.write('<html><head><title>%s</title></head></html>' %
552 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000553
554 return True
555
initial.commit94958cf2008-07-26 22:42:52 +0000556 def CacheNoStoreHandler(self):
557 """This request handler yields a page with the title set to the current
558 system time, and does not allow the page to be stored."""
559
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000560 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000561 return False
562
563 self.send_response(200)
564 self.send_header('Content-type', 'text/html')
565 self.send_header('Cache-Control', 'no-store')
566 self.end_headers()
567
maruel@google.come250a9b2009-03-10 17:39:46 +0000568 self.wfile.write('<html><head><title>%s</title></head></html>' %
569 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000570
571 return True
572
573 def CacheNoStoreMaxAgeHandler(self):
574 """This request handler yields a page with the title set to the current
575 system time, and does not allow the page to be stored even though max-age
576 of 60 seconds is specified."""
577
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000578 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000579 return False
580
581 self.send_response(200)
582 self.send_header('Content-type', 'text/html')
583 self.send_header('Cache-Control', 'max-age=60, no-store')
584 self.end_headers()
585
maruel@google.come250a9b2009-03-10 17:39:46 +0000586 self.wfile.write('<html><head><title>%s</title></head></html>' %
587 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000588
589 return True
590
591
592 def CacheNoTransformHandler(self):
593 """This request handler yields a page with the title set to the current
594 system time, and does not allow the content to transformed during
595 user-agent caching"""
596
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000597 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000598 return False
599
600 self.send_response(200)
601 self.send_header('Content-type', 'text/html')
602 self.send_header('Cache-Control', 'no-transform')
603 self.end_headers()
604
maruel@google.come250a9b2009-03-10 17:39:46 +0000605 self.wfile.write('<html><head><title>%s</title></head></html>' %
606 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000607
608 return True
609
610 def EchoHeader(self):
611 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000612 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000613
ananta@chromium.org56812d02011-04-07 17:52:05 +0000614 """This function echoes back the value of a specific request header"""
615 """while allowing caching for 16 hours."""
616 def EchoHeaderCache(self):
617 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000618
619 def EchoHeaderHelper(self, echo_header):
620 """This function echoes back the value of the request header passed in."""
621 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000622 return False
623
624 query_char = self.path.find('?')
625 if query_char != -1:
626 header_name = self.path[query_char+1:]
627
628 self.send_response(200)
629 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000630 if echo_header == '/echoheadercache':
631 self.send_header('Cache-control', 'max-age=60000')
632 else:
633 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000634 # insert a vary header to properly indicate that the cachability of this
635 # request is subject to value of the request header being echoed.
636 if len(header_name) > 0:
637 self.send_header('Vary', header_name)
638 self.end_headers()
639
640 if len(header_name) > 0:
641 self.wfile.write(self.headers.getheader(header_name))
642
643 return True
644
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000645 def ReadRequestBody(self):
646 """This function reads the body of the current HTTP request, handling
647 both plain and chunked transfer encoded requests."""
648
649 if self.headers.getheader('transfer-encoding') != 'chunked':
650 length = int(self.headers.getheader('content-length'))
651 return self.rfile.read(length)
652
653 # Read the request body as chunks.
654 body = ""
655 while True:
656 line = self.rfile.readline()
657 length = int(line, 16)
658 if length == 0:
659 self.rfile.readline()
660 break
661 body += self.rfile.read(length)
662 self.rfile.read(2)
663 return body
664
initial.commit94958cf2008-07-26 22:42:52 +0000665 def EchoHandler(self):
666 """This handler just echoes back the payload of the request, for testing
667 form submission."""
668
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000669 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000670 return False
671
672 self.send_response(200)
673 self.send_header('Content-type', 'text/html')
674 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000675 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000676 return True
677
678 def EchoTitleHandler(self):
679 """This handler is like Echo, but sets the page title to the request."""
680
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000681 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000682 return False
683
684 self.send_response(200)
685 self.send_header('Content-type', 'text/html')
686 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000687 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000688 self.wfile.write('<html><head><title>')
689 self.wfile.write(request)
690 self.wfile.write('</title></head></html>')
691 return True
692
693 def EchoAllHandler(self):
694 """This handler yields a (more) human-readable page listing information
695 about the request header & contents."""
696
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000697 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000698 return False
699
700 self.send_response(200)
701 self.send_header('Content-type', 'text/html')
702 self.end_headers()
703 self.wfile.write('<html><head><style>'
704 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
705 '</style></head><body>'
706 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000707 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000708 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000709
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000710 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000711 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000712 params = cgi.parse_qs(qs, keep_blank_values=1)
713
714 for param in params:
715 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000716
717 self.wfile.write('</pre>')
718
719 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
720
721 self.wfile.write('</body></html>')
722 return True
723
724 def DownloadHandler(self):
725 """This handler sends a downloadable file with or without reporting
726 the size (6K)."""
727
728 if self.path.startswith("/download-unknown-size"):
729 send_length = False
730 elif self.path.startswith("/download-known-size"):
731 send_length = True
732 else:
733 return False
734
735 #
736 # The test which uses this functionality is attempting to send
737 # small chunks of data to the client. Use a fairly large buffer
738 # so that we'll fill chrome's IO buffer enough to force it to
739 # actually write the data.
740 # See also the comments in the client-side of this test in
741 # download_uitest.cc
742 #
743 size_chunk1 = 35*1024
744 size_chunk2 = 10*1024
745
746 self.send_response(200)
747 self.send_header('Content-type', 'application/octet-stream')
748 self.send_header('Cache-Control', 'max-age=0')
749 if send_length:
750 self.send_header('Content-Length', size_chunk1 + size_chunk2)
751 self.end_headers()
752
753 # First chunk of data:
754 self.wfile.write("*" * size_chunk1)
755 self.wfile.flush()
756
757 # handle requests until one of them clears this flag.
758 self.server.waitForDownload = True
759 while self.server.waitForDownload:
760 self.server.handle_request()
761
762 # Second chunk of data:
763 self.wfile.write("*" * size_chunk2)
764 return True
765
766 def DownloadFinishHandler(self):
767 """This handler just tells the server to finish the current download."""
768
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000769 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000770 return False
771
772 self.server.waitForDownload = False
773 self.send_response(200)
774 self.send_header('Content-type', 'text/html')
775 self.send_header('Cache-Control', 'max-age=0')
776 self.end_headers()
777 return True
778
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000779 def _ReplaceFileData(self, data, query_parameters):
780 """Replaces matching substrings in a file.
781
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000782 If the 'replace_text' URL query parameter is present, it is expected to be
783 of the form old_text:new_text, which indicates that any old_text strings in
784 the file are replaced with new_text. Multiple 'replace_text' parameters may
785 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000786
787 If the parameters are not present, |data| is returned.
788 """
789 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000790 replace_text_values = query_dict.get('replace_text', [])
791 for replace_text_value in replace_text_values:
792 replace_text_args = replace_text_value.split(':')
793 if len(replace_text_args) != 2:
794 raise ValueError(
795 'replace_text must be of form old_text:new_text. Actual value: %s' %
796 replace_text_value)
797 old_text_b64, new_text_b64 = replace_text_args
798 old_text = base64.urlsafe_b64decode(old_text_b64)
799 new_text = base64.urlsafe_b64decode(new_text_b64)
800 data = data.replace(old_text, new_text)
801 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000802
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000803 def ZipFileHandler(self):
804 """This handler sends the contents of the requested file in compressed form.
805 Can pass in a parameter that specifies that the content length be
806 C - the compressed size (OK),
807 U - the uncompressed size (Non-standard, but handled),
808 S - less than compressed (OK because we keep going),
809 M - larger than compressed but less than uncompressed (an error),
810 L - larger than uncompressed (an error)
811 Example: compressedfiles/Picture_1.doc?C
812 """
813
814 prefix = "/compressedfiles/"
815 if not self.path.startswith(prefix):
816 return False
817
818 # Consume a request body if present.
819 if self.command == 'POST' or self.command == 'PUT' :
820 self.ReadRequestBody()
821
822 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
823
824 if not query in ('C', 'U', 'S', 'M', 'L'):
825 return False
826
827 sub_path = url_path[len(prefix):]
828 entries = sub_path.split('/')
829 file_path = os.path.join(self.server.data_dir, *entries)
830 if os.path.isdir(file_path):
831 file_path = os.path.join(file_path, 'index.html')
832
833 if not os.path.isfile(file_path):
834 print "File not found " + sub_path + " full path:" + file_path
835 self.send_error(404)
836 return True
837
838 f = open(file_path, "rb")
839 data = f.read()
840 uncompressed_len = len(data)
841 f.close()
842
843 # Compress the data.
844 data = zlib.compress(data)
845 compressed_len = len(data)
846
847 content_length = compressed_len
848 if query == 'U':
849 content_length = uncompressed_len
850 elif query == 'S':
851 content_length = compressed_len / 2
852 elif query == 'M':
853 content_length = (compressed_len + uncompressed_len) / 2
854 elif query == 'L':
855 content_length = compressed_len + uncompressed_len
856
857 self.send_response(200)
858 self.send_header('Content-type', 'application/msword')
859 self.send_header('Content-encoding', 'deflate')
860 self.send_header('Connection', 'close')
861 self.send_header('Content-Length', content_length)
862 self.send_header('ETag', '\'' + file_path + '\'')
863 self.end_headers()
864
865 self.wfile.write(data)
866
867 return True
868
initial.commit94958cf2008-07-26 22:42:52 +0000869 def FileHandler(self):
870 """This handler sends the contents of the requested file. Wow, it's like
871 a real webserver!"""
872
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000873 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000874 if not self.path.startswith(prefix):
875 return False
876
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000877 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000878 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000879 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000880
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000881 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
882 sub_path = url_path[len(prefix):]
883 entries = sub_path.split('/')
884 file_path = os.path.join(self.server.data_dir, *entries)
885 if os.path.isdir(file_path):
886 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000887
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000888 if not os.path.isfile(file_path):
889 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000890 self.send_error(404)
891 return True
892
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000893 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000894 data = f.read()
895 f.close()
896
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000897 data = self._ReplaceFileData(data, query)
898
initial.commit94958cf2008-07-26 22:42:52 +0000899 # If file.mock-http-headers exists, it contains the headers we
900 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000901 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000902 if os.path.isfile(headers_path):
903 f = open(headers_path, "r")
904
905 # "HTTP/1.1 200 OK"
906 response = f.readline()
907 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
908 self.send_response(int(status_code))
909
910 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000911 header_values = re.findall('(\S+):\s*(.*)', line)
912 if len(header_values) > 0:
913 # "name: value"
914 name, value = header_values[0]
915 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000916 f.close()
917 else:
918 # Could be more generic once we support mime-type sniffing, but for
919 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000920
921 range = self.headers.get('Range')
922 if range and range.startswith('bytes='):
923 # Note this doesn't handle all valid byte range values (i.e. open ended
924 # ones), just enough for what we needed so far.
925 range = range[6:].split('-')
926 start = int(range[0])
927 end = int(range[1])
928
929 self.send_response(206)
930 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
931 str(len(data))
932 self.send_header('Content-Range', content_range)
933 data = data[start: end + 1]
934 else:
935 self.send_response(200)
936
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000937 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000938 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000939 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000940 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000941 self.end_headers()
942
943 self.wfile.write(data)
944
945 return True
946
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000947 def SetCookieHandler(self):
948 """This handler just sets a cookie, for testing cookie handling."""
949
950 if not self._ShouldHandleRequest("/set-cookie"):
951 return False
952
953 query_char = self.path.find('?')
954 if query_char != -1:
955 cookie_values = self.path[query_char + 1:].split('&')
956 else:
957 cookie_values = ("",)
958 self.send_response(200)
959 self.send_header('Content-type', 'text/html')
960 for cookie_value in cookie_values:
961 self.send_header('Set-Cookie', '%s' % cookie_value)
962 self.end_headers()
963 for cookie_value in cookie_values:
964 self.wfile.write('%s' % cookie_value)
965 return True
966
initial.commit94958cf2008-07-26 22:42:52 +0000967 def AuthBasicHandler(self):
968 """This handler tests 'Basic' authentication. It just sends a page with
969 title 'user/pass' if you succeed."""
970
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000971 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000972 return False
973
974 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000975 expected_password = 'secret'
976 realm = 'testrealm'
977 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000978
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000979 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
980 query_params = cgi.parse_qs(query, True)
981 if 'set-cookie-if-challenged' in query_params:
982 set_cookie_if_challenged = True
983 if 'password' in query_params:
984 expected_password = query_params['password'][0]
985 if 'realm' in query_params:
986 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000987
initial.commit94958cf2008-07-26 22:42:52 +0000988 auth = self.headers.getheader('authorization')
989 try:
990 if not auth:
991 raise Exception('no auth')
992 b64str = re.findall(r'Basic (\S+)', auth)[0]
993 userpass = base64.b64decode(b64str)
994 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000995 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +0000996 raise Exception('wrong password')
997 except Exception, e:
998 # Authentication failed.
999 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001000 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +00001001 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001002 if set_cookie_if_challenged:
1003 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001004 self.end_headers()
1005 self.wfile.write('<html><head>')
1006 self.wfile.write('<title>Denied: %s</title>' % e)
1007 self.wfile.write('</head><body>')
1008 self.wfile.write('auth=%s<p>' % auth)
1009 self.wfile.write('b64str=%s<p>' % b64str)
1010 self.wfile.write('username: %s<p>' % username)
1011 self.wfile.write('userpass: %s<p>' % userpass)
1012 self.wfile.write('password: %s<p>' % password)
1013 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1014 self.wfile.write('</body></html>')
1015 return True
1016
1017 # Authentication successful. (Return a cachable response to allow for
1018 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001019 old_protocol_version = self.protocol_version
1020 self.protocol_version = "HTTP/1.1"
1021
initial.commit94958cf2008-07-26 22:42:52 +00001022 if_none_match = self.headers.getheader('if-none-match')
1023 if if_none_match == "abc":
1024 self.send_response(304)
1025 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001026 elif url_path.endswith(".gif"):
1027 # Using chrome/test/data/google/logo.gif as the test image
1028 test_image_path = ['google', 'logo.gif']
1029 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1030 if not os.path.isfile(gif_path):
1031 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001032 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001033 return True
1034
1035 f = open(gif_path, "rb")
1036 data = f.read()
1037 f.close()
1038
1039 self.send_response(200)
1040 self.send_header('Content-type', 'image/gif')
1041 self.send_header('Cache-control', 'max-age=60000')
1042 self.send_header('Etag', 'abc')
1043 self.end_headers()
1044 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001045 else:
1046 self.send_response(200)
1047 self.send_header('Content-type', 'text/html')
1048 self.send_header('Cache-control', 'max-age=60000')
1049 self.send_header('Etag', 'abc')
1050 self.end_headers()
1051 self.wfile.write('<html><head>')
1052 self.wfile.write('<title>%s/%s</title>' % (username, password))
1053 self.wfile.write('</head><body>')
1054 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001055 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001056 self.wfile.write('</body></html>')
1057
rvargas@google.com54453b72011-05-19 01:11:11 +00001058 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001059 return True
1060
tonyg@chromium.org75054202010-03-31 22:06:10 +00001061 def GetNonce(self, force_reset=False):
1062 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001063
tonyg@chromium.org75054202010-03-31 22:06:10 +00001064 This is a fake implementation. A real implementation would only use a given
1065 nonce a single time (hence the name n-once). However, for the purposes of
1066 unittesting, we don't care about the security of the nonce.
1067
1068 Args:
1069 force_reset: Iff set, the nonce will be changed. Useful for testing the
1070 "stale" response.
1071 """
1072 if force_reset or not self.server.nonce_time:
1073 self.server.nonce_time = time.time()
1074 return _new_md5('privatekey%s%d' %
1075 (self.path, self.server.nonce_time)).hexdigest()
1076
1077 def AuthDigestHandler(self):
1078 """This handler tests 'Digest' authentication.
1079
1080 It just sends a page with title 'user/pass' if you succeed.
1081
1082 A stale response is sent iff "stale" is present in the request path.
1083 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001084 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001085 return False
1086
tonyg@chromium.org75054202010-03-31 22:06:10 +00001087 stale = 'stale' in self.path
1088 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001089 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001090 password = 'secret'
1091 realm = 'testrealm'
1092
1093 auth = self.headers.getheader('authorization')
1094 pairs = {}
1095 try:
1096 if not auth:
1097 raise Exception('no auth')
1098 if not auth.startswith('Digest'):
1099 raise Exception('not digest')
1100 # Pull out all the name="value" pairs as a dictionary.
1101 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1102
1103 # Make sure it's all valid.
1104 if pairs['nonce'] != nonce:
1105 raise Exception('wrong nonce')
1106 if pairs['opaque'] != opaque:
1107 raise Exception('wrong opaque')
1108
1109 # Check the 'response' value and make sure it matches our magic hash.
1110 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001111 hash_a1 = _new_md5(
1112 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001113 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001114 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001115 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001116 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1117 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001118 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001119
1120 if pairs['response'] != response:
1121 raise Exception('wrong password')
1122 except Exception, e:
1123 # Authentication failed.
1124 self.send_response(401)
1125 hdr = ('Digest '
1126 'realm="%s", '
1127 'domain="/", '
1128 'qop="auth", '
1129 'algorithm=MD5, '
1130 'nonce="%s", '
1131 'opaque="%s"') % (realm, nonce, opaque)
1132 if stale:
1133 hdr += ', stale="TRUE"'
1134 self.send_header('WWW-Authenticate', hdr)
1135 self.send_header('Content-type', 'text/html')
1136 self.end_headers()
1137 self.wfile.write('<html><head>')
1138 self.wfile.write('<title>Denied: %s</title>' % e)
1139 self.wfile.write('</head><body>')
1140 self.wfile.write('auth=%s<p>' % auth)
1141 self.wfile.write('pairs=%s<p>' % pairs)
1142 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1143 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1144 self.wfile.write('</body></html>')
1145 return True
1146
1147 # Authentication successful.
1148 self.send_response(200)
1149 self.send_header('Content-type', 'text/html')
1150 self.end_headers()
1151 self.wfile.write('<html><head>')
1152 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1153 self.wfile.write('</head><body>')
1154 self.wfile.write('auth=%s<p>' % auth)
1155 self.wfile.write('pairs=%s<p>' % pairs)
1156 self.wfile.write('</body></html>')
1157
1158 return True
1159
1160 def SlowServerHandler(self):
1161 """Wait for the user suggested time before responding. The syntax is
1162 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001163 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001164 return False
1165 query_char = self.path.find('?')
1166 wait_sec = 1.0
1167 if query_char >= 0:
1168 try:
1169 wait_sec = int(self.path[query_char + 1:])
1170 except ValueError:
1171 pass
1172 time.sleep(wait_sec)
1173 self.send_response(200)
1174 self.send_header('Content-type', 'text/plain')
1175 self.end_headers()
1176 self.wfile.write("waited %d seconds" % wait_sec)
1177 return True
1178
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001179 def ChunkedServerHandler(self):
1180 """Send chunked response. Allows to specify chunks parameters:
1181 - waitBeforeHeaders - ms to wait before sending headers
1182 - waitBetweenChunks - ms to wait between chunks
1183 - chunkSize - size of each chunk in bytes
1184 - chunksNumber - number of chunks
1185 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1186 waits one second, then sends headers and five chunks five bytes each."""
1187 if not self._ShouldHandleRequest("/chunked"):
1188 return False
1189 query_char = self.path.find('?')
1190 chunkedSettings = {'waitBeforeHeaders' : 0,
1191 'waitBetweenChunks' : 0,
1192 'chunkSize' : 5,
1193 'chunksNumber' : 5}
1194 if query_char >= 0:
1195 params = self.path[query_char + 1:].split('&')
1196 for param in params:
1197 keyValue = param.split('=')
1198 if len(keyValue) == 2:
1199 try:
1200 chunkedSettings[keyValue[0]] = int(keyValue[1])
1201 except ValueError:
1202 pass
1203 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1204 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1205 self.send_response(200)
1206 self.send_header('Content-type', 'text/plain')
1207 self.send_header('Connection', 'close')
1208 self.send_header('Transfer-Encoding', 'chunked')
1209 self.end_headers()
1210 # Chunked encoding: sending all chunks, then final zero-length chunk and
1211 # then final CRLF.
1212 for i in range(0, chunkedSettings['chunksNumber']):
1213 if i > 0:
1214 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1215 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1216 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1217 self.sendChunkHelp('')
1218 return True
1219
initial.commit94958cf2008-07-26 22:42:52 +00001220 def ContentTypeHandler(self):
1221 """Returns a string of html with the given content type. E.g.,
1222 /contenttype?text/css returns an html file with the Content-Type
1223 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001224 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001225 return False
1226 query_char = self.path.find('?')
1227 content_type = self.path[query_char + 1:].strip()
1228 if not content_type:
1229 content_type = 'text/html'
1230 self.send_response(200)
1231 self.send_header('Content-Type', content_type)
1232 self.end_headers()
1233 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1234 return True
1235
creis@google.com2f4f6a42011-03-25 19:44:19 +00001236 def NoContentHandler(self):
1237 """Returns a 204 No Content response."""
1238 if not self._ShouldHandleRequest("/nocontent"):
1239 return False
1240 self.send_response(204)
1241 self.end_headers()
1242 return True
1243
initial.commit94958cf2008-07-26 22:42:52 +00001244 def ServerRedirectHandler(self):
1245 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001246 '/server-redirect?http://foo.bar/asdf' to redirect to
1247 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001248
1249 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001250 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001251 return False
1252
1253 query_char = self.path.find('?')
1254 if query_char < 0 or len(self.path) <= query_char + 1:
1255 self.sendRedirectHelp(test_name)
1256 return True
1257 dest = self.path[query_char + 1:]
1258
1259 self.send_response(301) # moved permanently
1260 self.send_header('Location', dest)
1261 self.send_header('Content-type', 'text/html')
1262 self.end_headers()
1263 self.wfile.write('<html><head>')
1264 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1265
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001266 return True
initial.commit94958cf2008-07-26 22:42:52 +00001267
1268 def ClientRedirectHandler(self):
1269 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001270 '/client-redirect?http://foo.bar/asdf' to redirect to
1271 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001272
1273 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001274 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001275 return False
1276
1277 query_char = self.path.find('?');
1278 if query_char < 0 or len(self.path) <= query_char + 1:
1279 self.sendRedirectHelp(test_name)
1280 return True
1281 dest = self.path[query_char + 1:]
1282
1283 self.send_response(200)
1284 self.send_header('Content-type', 'text/html')
1285 self.end_headers()
1286 self.wfile.write('<html><head>')
1287 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1288 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1289
1290 return True
1291
tony@chromium.org03266982010-03-05 03:18:42 +00001292 def MultipartHandler(self):
1293 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001294 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001295 if not self._ShouldHandleRequest(test_name):
1296 return False
1297
1298 num_frames = 10
1299 bound = '12345'
1300 self.send_response(200)
1301 self.send_header('Content-type',
1302 'multipart/x-mixed-replace;boundary=' + bound)
1303 self.end_headers()
1304
1305 for i in xrange(num_frames):
1306 self.wfile.write('--' + bound + '\r\n')
1307 self.wfile.write('Content-type: text/html\r\n\r\n')
1308 self.wfile.write('<title>page ' + str(i) + '</title>')
1309 self.wfile.write('page ' + str(i))
1310
1311 self.wfile.write('--' + bound + '--')
1312 return True
1313
tony@chromium.org4cb88302011-09-27 22:13:49 +00001314 def MultipartSlowHandler(self):
1315 """Send a multipart response (3 text/html pages) with a slight delay
1316 between each page. This is similar to how some pages show status using
1317 multipart."""
1318 test_name = '/multipart-slow'
1319 if not self._ShouldHandleRequest(test_name):
1320 return False
1321
1322 num_frames = 3
1323 bound = '12345'
1324 self.send_response(200)
1325 self.send_header('Content-type',
1326 'multipart/x-mixed-replace;boundary=' + bound)
1327 self.end_headers()
1328
1329 for i in xrange(num_frames):
1330 self.wfile.write('--' + bound + '\r\n')
1331 self.wfile.write('Content-type: text/html\r\n\r\n')
1332 time.sleep(0.25)
1333 if i == 2:
1334 self.wfile.write('<title>PASS</title>')
1335 else:
1336 self.wfile.write('<title>page ' + str(i) + '</title>')
1337 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1338
1339 self.wfile.write('--' + bound + '--')
1340 return True
1341
initial.commit94958cf2008-07-26 22:42:52 +00001342 def DefaultResponseHandler(self):
1343 """This is the catch-all response handler for requests that aren't handled
1344 by one of the special handlers above.
1345 Note that we specify the content-length as without it the https connection
1346 is not closed properly (and the browser keeps expecting data)."""
1347
1348 contents = "Default response given for path: " + self.path
1349 self.send_response(200)
1350 self.send_header('Content-type', 'text/html')
1351 self.send_header("Content-Length", len(contents))
1352 self.end_headers()
1353 self.wfile.write(contents)
1354 return True
1355
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001356 def RedirectConnectHandler(self):
1357 """Sends a redirect to the CONNECT request for www.redirect.com. This
1358 response is not specified by the RFC, so the browser should not follow
1359 the redirect."""
1360
1361 if (self.path.find("www.redirect.com") < 0):
1362 return False
1363
1364 dest = "http://www.destination.com/foo.js"
1365
1366 self.send_response(302) # moved temporarily
1367 self.send_header('Location', dest)
1368 self.send_header('Connection', 'close')
1369 self.end_headers()
1370 return True
1371
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001372 def ServerAuthConnectHandler(self):
1373 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1374 response doesn't make sense because the proxy server cannot request
1375 server authentication."""
1376
1377 if (self.path.find("www.server-auth.com") < 0):
1378 return False
1379
1380 challenge = 'Basic realm="WallyWorld"'
1381
1382 self.send_response(401) # unauthorized
1383 self.send_header('WWW-Authenticate', challenge)
1384 self.send_header('Connection', 'close')
1385 self.end_headers()
1386 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001387
1388 def DefaultConnectResponseHandler(self):
1389 """This is the catch-all response handler for CONNECT requests that aren't
1390 handled by one of the special handlers above. Real Web servers respond
1391 with 400 to CONNECT requests."""
1392
1393 contents = "Your client has issued a malformed or illegal request."
1394 self.send_response(400) # bad request
1395 self.send_header('Content-type', 'text/html')
1396 self.send_header("Content-Length", len(contents))
1397 self.end_headers()
1398 self.wfile.write(contents)
1399 return True
1400
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001401 def DeviceManagementHandler(self):
1402 """Delegates to the device management service used for cloud policy."""
1403 if not self._ShouldHandleRequest("/device_management"):
1404 return False
1405
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001406 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001407
1408 if not self.server._device_management_handler:
1409 import device_management
1410 policy_path = os.path.join(self.server.data_dir, 'device_management')
1411 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001412 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001413 self.server.policy_keys,
1414 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001415
1416 http_response, raw_reply = (
1417 self.server._device_management_handler.HandleRequest(self.path,
1418 self.headers,
1419 raw_request))
1420 self.send_response(http_response)
1421 self.end_headers()
1422 self.wfile.write(raw_reply)
1423 return True
1424
initial.commit94958cf2008-07-26 22:42:52 +00001425 # called by the redirect handling function when there is no parameter
1426 def sendRedirectHelp(self, redirect_name):
1427 self.send_response(200)
1428 self.send_header('Content-type', 'text/html')
1429 self.end_headers()
1430 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1431 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1432 self.wfile.write('</body></html>')
1433
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001434 # called by chunked handling function
1435 def sendChunkHelp(self, chunk):
1436 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1437 self.wfile.write('%X\r\n' % len(chunk))
1438 self.wfile.write(chunk)
1439 self.wfile.write('\r\n')
1440
akalin@chromium.org154bb132010-11-12 02:20:27 +00001441
1442class SyncPageHandler(BasePageHandler):
1443 """Handler for the main HTTP sync server."""
1444
1445 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001446 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001447 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001448 self.ChromiumSyncDisableNotificationsOpHandler,
1449 self.ChromiumSyncEnableNotificationsOpHandler,
1450 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001451 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001452 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001453 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001454 self.ChromiumSyncErrorOpHandler,
1455 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001456
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001457 post_handlers = [self.ChromiumSyncCommandHandler,
1458 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001459 BasePageHandler.__init__(self, request, client_address,
1460 sync_http_server, [], get_handlers,
1461 post_handlers, [])
1462
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001463
akalin@chromium.org154bb132010-11-12 02:20:27 +00001464 def ChromiumSyncTimeHandler(self):
1465 """Handle Chromium sync .../time requests.
1466
1467 The syncer sometimes checks server reachability by examining /time.
1468 """
1469 test_name = "/chromiumsync/time"
1470 if not self._ShouldHandleRequest(test_name):
1471 return False
1472
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001473 # Chrome hates it if we send a response before reading the request.
1474 if self.headers.getheader('content-length'):
1475 length = int(self.headers.getheader('content-length'))
1476 raw_request = self.rfile.read(length)
1477
akalin@chromium.org154bb132010-11-12 02:20:27 +00001478 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001479 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001480 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001481 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001482 return True
1483
1484 def ChromiumSyncCommandHandler(self):
1485 """Handle a chromiumsync command arriving via http.
1486
1487 This covers all sync protocol commands: authentication, getupdates, and
1488 commit.
1489 """
1490 test_name = "/chromiumsync/command"
1491 if not self._ShouldHandleRequest(test_name):
1492 return False
1493
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001494 length = int(self.headers.getheader('content-length'))
1495 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001496 http_response = 200
1497 raw_reply = None
1498 if not self.server.GetAuthenticated():
1499 http_response = 401
1500 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1501 else:
1502 http_response, raw_reply = self.server.HandleCommand(
1503 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001504
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001505 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001506 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001507 if http_response == 401:
1508 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001509 self.end_headers()
1510 self.wfile.write(raw_reply)
1511 return True
1512
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001513 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001514 test_name = "/chromiumsync/migrate"
1515 if not self._ShouldHandleRequest(test_name):
1516 return False
1517
1518 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1519 self.path)
1520 self.send_response(http_response)
1521 self.send_header('Content-Type', 'text/html')
1522 self.send_header('Content-Length', len(raw_reply))
1523 self.end_headers()
1524 self.wfile.write(raw_reply)
1525 return True
1526
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001527 def ChromiumSyncCredHandler(self):
1528 test_name = "/chromiumsync/cred"
1529 if not self._ShouldHandleRequest(test_name):
1530 return False
1531 try:
1532 query = urlparse.urlparse(self.path)[4]
1533 cred_valid = urlparse.parse_qs(query)['valid']
1534 if cred_valid[0] == 'True':
1535 self.server.SetAuthenticated(True)
1536 else:
1537 self.server.SetAuthenticated(False)
1538 except:
1539 self.server.SetAuthenticated(False)
1540
1541 http_response = 200
1542 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1543 self.send_response(http_response)
1544 self.send_header('Content-Type', 'text/html')
1545 self.send_header('Content-Length', len(raw_reply))
1546 self.end_headers()
1547 self.wfile.write(raw_reply)
1548 return True
1549
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001550 def ChromiumSyncDisableNotificationsOpHandler(self):
1551 test_name = "/chromiumsync/disablenotifications"
1552 if not self._ShouldHandleRequest(test_name):
1553 return False
1554 self.server.GetXmppServer().DisableNotifications()
1555 result = 200
1556 raw_reply = ('<html><title>Notifications disabled</title>'
1557 '<H1>Notifications disabled</H1></html>')
1558 self.send_response(result)
1559 self.send_header('Content-Type', 'text/html')
1560 self.send_header('Content-Length', len(raw_reply))
1561 self.end_headers()
1562 self.wfile.write(raw_reply)
1563 return True;
1564
1565 def ChromiumSyncEnableNotificationsOpHandler(self):
1566 test_name = "/chromiumsync/enablenotifications"
1567 if not self._ShouldHandleRequest(test_name):
1568 return False
1569 self.server.GetXmppServer().EnableNotifications()
1570 result = 200
1571 raw_reply = ('<html><title>Notifications enabled</title>'
1572 '<H1>Notifications enabled</H1></html>')
1573 self.send_response(result)
1574 self.send_header('Content-Type', 'text/html')
1575 self.send_header('Content-Length', len(raw_reply))
1576 self.end_headers()
1577 self.wfile.write(raw_reply)
1578 return True;
1579
1580 def ChromiumSyncSendNotificationOpHandler(self):
1581 test_name = "/chromiumsync/sendnotification"
1582 if not self._ShouldHandleRequest(test_name):
1583 return False
1584 query = urlparse.urlparse(self.path)[4]
1585 query_params = urlparse.parse_qs(query)
1586 channel = ''
1587 data = ''
1588 if 'channel' in query_params:
1589 channel = query_params['channel'][0]
1590 if 'data' in query_params:
1591 data = query_params['data'][0]
1592 self.server.GetXmppServer().SendNotification(channel, data)
1593 result = 200
1594 raw_reply = ('<html><title>Notification sent</title>'
1595 '<H1>Notification sent with channel "%s" '
1596 'and data "%s"</H1></html>'
1597 % (channel, data))
1598 self.send_response(result)
1599 self.send_header('Content-Type', 'text/html')
1600 self.send_header('Content-Length', len(raw_reply))
1601 self.end_headers()
1602 self.wfile.write(raw_reply)
1603 return True;
1604
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001605 def ChromiumSyncBirthdayErrorOpHandler(self):
1606 test_name = "/chromiumsync/birthdayerror"
1607 if not self._ShouldHandleRequest(test_name):
1608 return False
1609 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1610 self.send_response(result)
1611 self.send_header('Content-Type', 'text/html')
1612 self.send_header('Content-Length', len(raw_reply))
1613 self.end_headers()
1614 self.wfile.write(raw_reply)
1615 return True;
1616
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001617 def ChromiumSyncTransientErrorOpHandler(self):
1618 test_name = "/chromiumsync/transienterror"
1619 if not self._ShouldHandleRequest(test_name):
1620 return False
1621 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1622 self.send_response(result)
1623 self.send_header('Content-Type', 'text/html')
1624 self.send_header('Content-Length', len(raw_reply))
1625 self.end_headers()
1626 self.wfile.write(raw_reply)
1627 return True;
1628
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001629 def ChromiumSyncErrorOpHandler(self):
1630 test_name = "/chromiumsync/error"
1631 if not self._ShouldHandleRequest(test_name):
1632 return False
1633 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1634 self.path)
1635 self.send_response(result)
1636 self.send_header('Content-Type', 'text/html')
1637 self.send_header('Content-Length', len(raw_reply))
1638 self.end_headers()
1639 self.wfile.write(raw_reply)
1640 return True;
1641
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001642 def ChromiumSyncSyncTabsOpHandler(self):
1643 test_name = "/chromiumsync/synctabs"
1644 if not self._ShouldHandleRequest(test_name):
1645 return False
1646 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1647 self.send_response(result)
1648 self.send_header('Content-Type', 'text/html')
1649 self.send_header('Content-Length', len(raw_reply))
1650 self.end_headers()
1651 self.wfile.write(raw_reply)
1652 return True;
1653
akalin@chromium.org154bb132010-11-12 02:20:27 +00001654
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001655def MakeDataDir():
1656 if options.data_dir:
1657 if not os.path.isdir(options.data_dir):
1658 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1659 return None
1660 my_data_dir = options.data_dir
1661 else:
1662 # Create the default path to our data dir, relative to the exe dir.
1663 my_data_dir = os.path.dirname(sys.argv[0])
1664 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001665 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001666
1667 #TODO(ibrar): Must use Find* funtion defined in google\tools
1668 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1669
1670 return my_data_dir
1671
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001672
1673class TCPEchoHandler(SocketServer.BaseRequestHandler):
1674 """The RequestHandler class for TCP echo server.
1675
1676 It is instantiated once per connection to the server, and overrides the
1677 handle() method to implement communication to the client.
1678 """
1679
1680 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001681 """Handles the request from the client and constructs a response."""
1682
1683 data = self.request.recv(65536).strip()
1684 # Verify the "echo request" message received from the client. Send back
1685 # "echo response" message if "echo request" message is valid.
1686 try:
1687 return_data = echo_message.GetEchoResponseData(data)
1688 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001689 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001690 except ValueError:
1691 return
1692
1693 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001694
1695
1696class UDPEchoHandler(SocketServer.BaseRequestHandler):
1697 """The RequestHandler class for UDP echo server.
1698
1699 It is instantiated once per connection to the server, and overrides the
1700 handle() method to implement communication to the client.
1701 """
1702
1703 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001704 """Handles the request from the client and constructs a response."""
1705
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001706 data = self.request[0].strip()
1707 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001708 # Verify the "echo request" message received from the client. Send back
1709 # "echo response" message if "echo request" message is valid.
1710 try:
1711 return_data = echo_message.GetEchoResponseData(data)
1712 if not return_data:
1713 return
1714 except ValueError:
1715 return
1716 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001717
1718
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001719class FileMultiplexer:
1720 def __init__(self, fd1, fd2) :
1721 self.__fd1 = fd1
1722 self.__fd2 = fd2
1723
1724 def __del__(self) :
1725 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1726 self.__fd1.close()
1727 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1728 self.__fd2.close()
1729
1730 def write(self, text) :
1731 self.__fd1.write(text)
1732 self.__fd2.write(text)
1733
1734 def flush(self) :
1735 self.__fd1.flush()
1736 self.__fd2.flush()
1737
initial.commit94958cf2008-07-26 22:42:52 +00001738def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001739 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001740 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001741 if options.log_to_console:
1742 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1743 else:
1744 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001745
1746 port = options.port
1747
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001748 server_data = {}
1749
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001750 if options.server_type == SERVER_HTTP:
1751 if options.cert:
1752 # let's make sure the cert file exists.
1753 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001754 print 'specified server cert file not found: ' + options.cert + \
1755 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001756 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001757 for ca_cert in options.ssl_client_ca:
1758 if not os.path.isfile(ca_cert):
1759 print 'specified trusted client CA file not found: ' + ca_cert + \
1760 ' exiting...'
1761 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001762 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001763 options.ssl_client_auth, options.ssl_client_ca,
1764 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001765 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001766 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001767 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001768 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001769
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001770 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001771 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001772 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001773 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001774 server.policy_keys = options.policy_keys
1775 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001776 elif options.server_type == SERVER_SYNC:
1777 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1778 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001779 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1780 server_data['port'] = server.server_port
1781 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001782 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001783 # Used for generating the key (randomly) that encodes the "echo request"
1784 # message.
1785 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001786 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1787 print 'Echo TCP server started on port %d...' % server.server_port
1788 server_data['port'] = server.server_port
1789 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001790 # Used for generating the key (randomly) that encodes the "echo request"
1791 # message.
1792 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001793 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1794 print 'Echo UDP server started on port %d...' % server.server_port
1795 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001796 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001797 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001798 my_data_dir = MakeDataDir()
1799
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001800 # Instantiate a dummy authorizer for managing 'virtual' users
1801 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1802
1803 # Define a new user having full r/w permissions and a read-only
1804 # anonymous user
1805 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1806
1807 authorizer.add_anonymous(my_data_dir)
1808
1809 # Instantiate FTP handler class
1810 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1811 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001812
1813 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001814 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1815 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001816
1817 # Instantiate FTP server class and listen to 127.0.0.1:port
1818 address = ('127.0.0.1', port)
1819 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001820 server_data['port'] = server.socket.getsockname()[1]
1821 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001822
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001823 # Notify the parent that we've started. (BaseServer subclasses
1824 # bind their sockets on construction.)
1825 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001826 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001827 server_data_len = len(server_data_json)
1828 print 'sending server_data: %s (%d bytes)' % (
1829 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001830 if sys.platform == 'win32':
1831 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1832 else:
1833 fd = options.startup_pipe
1834 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001835 # First write the data length as an unsigned 4-byte value. This
1836 # is _not_ using network byte ordering since the other end of the
1837 # pipe is on the same machine.
1838 startup_pipe.write(struct.pack('=L', server_data_len))
1839 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001840 startup_pipe.close()
1841
initial.commit94958cf2008-07-26 22:42:52 +00001842 try:
1843 server.serve_forever()
1844 except KeyboardInterrupt:
1845 print 'shutting down server'
1846 server.stop = True
1847
1848if __name__ == '__main__':
1849 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001850 option_parser.add_option("-f", '--ftp', action='store_const',
1851 const=SERVER_FTP, default=SERVER_HTTP,
1852 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001853 help='start up an FTP server.')
1854 option_parser.add_option('', '--sync', action='store_const',
1855 const=SERVER_SYNC, default=SERVER_HTTP,
1856 dest='server_type',
1857 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001858 option_parser.add_option('', '--tcp-echo', action='store_const',
1859 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1860 dest='server_type',
1861 help='start up a tcp echo server.')
1862 option_parser.add_option('', '--udp-echo', action='store_const',
1863 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1864 dest='server_type',
1865 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001866 option_parser.add_option('', '--log-to-console', action='store_const',
1867 const=True, default=False,
1868 dest='log_to_console',
1869 help='Enables or disables sys.stdout logging to '
1870 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001871 option_parser.add_option('', '--port', default='0', type='int',
1872 help='Port used by the server. If unspecified, the '
1873 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001874 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001875 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001876 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001877 help='Specify that https should be used, specify '
1878 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001879 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001880 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1881 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001882 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1883 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001884 'should include the CA named in the subject of '
1885 'the DER-encoded certificate contained in the '
1886 'specified file. This option may appear multiple '
1887 'times, indicating multiple CA names should be '
1888 'sent in the request.')
1889 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1890 help='Specify the bulk encryption algorithm(s)'
1891 'that will be accepted by the SSL server. Valid '
1892 'values are "aes256", "aes128", "3des", "rc4". If '
1893 'omitted, all algorithms will be used. This '
1894 'option may appear multiple times, indicating '
1895 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001896 option_parser.add_option('', '--file-root-url', default='/files/',
1897 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001898 option_parser.add_option('', '--startup-pipe', type='int',
1899 dest='startup_pipe',
1900 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001901 option_parser.add_option('', '--policy-key', action='append',
1902 dest='policy_keys',
1903 help='Specify a path to a PEM-encoded private key '
1904 'to use for policy signing. May be specified '
1905 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001906 'the server. If ther server has multiple keys, it '
1907 'will rotate through them in at each request a '
1908 'round-robin fashion. The server will generate a '
1909 'random key if none is specified on the command '
1910 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001911 option_parser.add_option('', '--policy-user', default='user@example.com',
1912 dest='policy_user',
1913 help='Specify the user name the server should '
1914 'report back to the client as the user owning the '
1915 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001916 options, args = option_parser.parse_args()
1917
1918 sys.exit(main(options, args))