blob: 41470044eb2a73d549774e62bed10780faea1bb0 [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)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001421 if (http_response == 200):
1422 self.send_header('Content-type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001423 self.end_headers()
1424 self.wfile.write(raw_reply)
1425 return True
1426
initial.commit94958cf2008-07-26 22:42:52 +00001427 # called by the redirect handling function when there is no parameter
1428 def sendRedirectHelp(self, redirect_name):
1429 self.send_response(200)
1430 self.send_header('Content-type', 'text/html')
1431 self.end_headers()
1432 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1433 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1434 self.wfile.write('</body></html>')
1435
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001436 # called by chunked handling function
1437 def sendChunkHelp(self, chunk):
1438 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1439 self.wfile.write('%X\r\n' % len(chunk))
1440 self.wfile.write(chunk)
1441 self.wfile.write('\r\n')
1442
akalin@chromium.org154bb132010-11-12 02:20:27 +00001443
1444class SyncPageHandler(BasePageHandler):
1445 """Handler for the main HTTP sync server."""
1446
1447 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001448 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001449 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001450 self.ChromiumSyncDisableNotificationsOpHandler,
1451 self.ChromiumSyncEnableNotificationsOpHandler,
1452 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001453 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001454 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001455 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001456 self.ChromiumSyncErrorOpHandler,
1457 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001458
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001459 post_handlers = [self.ChromiumSyncCommandHandler,
1460 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001461 BasePageHandler.__init__(self, request, client_address,
1462 sync_http_server, [], get_handlers,
1463 post_handlers, [])
1464
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001465
akalin@chromium.org154bb132010-11-12 02:20:27 +00001466 def ChromiumSyncTimeHandler(self):
1467 """Handle Chromium sync .../time requests.
1468
1469 The syncer sometimes checks server reachability by examining /time.
1470 """
1471 test_name = "/chromiumsync/time"
1472 if not self._ShouldHandleRequest(test_name):
1473 return False
1474
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001475 # Chrome hates it if we send a response before reading the request.
1476 if self.headers.getheader('content-length'):
1477 length = int(self.headers.getheader('content-length'))
1478 raw_request = self.rfile.read(length)
1479
akalin@chromium.org154bb132010-11-12 02:20:27 +00001480 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001481 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001482 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001483 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001484 return True
1485
1486 def ChromiumSyncCommandHandler(self):
1487 """Handle a chromiumsync command arriving via http.
1488
1489 This covers all sync protocol commands: authentication, getupdates, and
1490 commit.
1491 """
1492 test_name = "/chromiumsync/command"
1493 if not self._ShouldHandleRequest(test_name):
1494 return False
1495
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001496 length = int(self.headers.getheader('content-length'))
1497 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001498 http_response = 200
1499 raw_reply = None
1500 if not self.server.GetAuthenticated():
1501 http_response = 401
1502 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1503 else:
1504 http_response, raw_reply = self.server.HandleCommand(
1505 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001506
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001507 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001508 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001509 if http_response == 401:
1510 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001511 self.end_headers()
1512 self.wfile.write(raw_reply)
1513 return True
1514
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001515 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001516 test_name = "/chromiumsync/migrate"
1517 if not self._ShouldHandleRequest(test_name):
1518 return False
1519
1520 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1521 self.path)
1522 self.send_response(http_response)
1523 self.send_header('Content-Type', 'text/html')
1524 self.send_header('Content-Length', len(raw_reply))
1525 self.end_headers()
1526 self.wfile.write(raw_reply)
1527 return True
1528
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001529 def ChromiumSyncCredHandler(self):
1530 test_name = "/chromiumsync/cred"
1531 if not self._ShouldHandleRequest(test_name):
1532 return False
1533 try:
1534 query = urlparse.urlparse(self.path)[4]
1535 cred_valid = urlparse.parse_qs(query)['valid']
1536 if cred_valid[0] == 'True':
1537 self.server.SetAuthenticated(True)
1538 else:
1539 self.server.SetAuthenticated(False)
1540 except:
1541 self.server.SetAuthenticated(False)
1542
1543 http_response = 200
1544 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1545 self.send_response(http_response)
1546 self.send_header('Content-Type', 'text/html')
1547 self.send_header('Content-Length', len(raw_reply))
1548 self.end_headers()
1549 self.wfile.write(raw_reply)
1550 return True
1551
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001552 def ChromiumSyncDisableNotificationsOpHandler(self):
1553 test_name = "/chromiumsync/disablenotifications"
1554 if not self._ShouldHandleRequest(test_name):
1555 return False
1556 self.server.GetXmppServer().DisableNotifications()
1557 result = 200
1558 raw_reply = ('<html><title>Notifications disabled</title>'
1559 '<H1>Notifications disabled</H1></html>')
1560 self.send_response(result)
1561 self.send_header('Content-Type', 'text/html')
1562 self.send_header('Content-Length', len(raw_reply))
1563 self.end_headers()
1564 self.wfile.write(raw_reply)
1565 return True;
1566
1567 def ChromiumSyncEnableNotificationsOpHandler(self):
1568 test_name = "/chromiumsync/enablenotifications"
1569 if not self._ShouldHandleRequest(test_name):
1570 return False
1571 self.server.GetXmppServer().EnableNotifications()
1572 result = 200
1573 raw_reply = ('<html><title>Notifications enabled</title>'
1574 '<H1>Notifications enabled</H1></html>')
1575 self.send_response(result)
1576 self.send_header('Content-Type', 'text/html')
1577 self.send_header('Content-Length', len(raw_reply))
1578 self.end_headers()
1579 self.wfile.write(raw_reply)
1580 return True;
1581
1582 def ChromiumSyncSendNotificationOpHandler(self):
1583 test_name = "/chromiumsync/sendnotification"
1584 if not self._ShouldHandleRequest(test_name):
1585 return False
1586 query = urlparse.urlparse(self.path)[4]
1587 query_params = urlparse.parse_qs(query)
1588 channel = ''
1589 data = ''
1590 if 'channel' in query_params:
1591 channel = query_params['channel'][0]
1592 if 'data' in query_params:
1593 data = query_params['data'][0]
1594 self.server.GetXmppServer().SendNotification(channel, data)
1595 result = 200
1596 raw_reply = ('<html><title>Notification sent</title>'
1597 '<H1>Notification sent with channel "%s" '
1598 'and data "%s"</H1></html>'
1599 % (channel, data))
1600 self.send_response(result)
1601 self.send_header('Content-Type', 'text/html')
1602 self.send_header('Content-Length', len(raw_reply))
1603 self.end_headers()
1604 self.wfile.write(raw_reply)
1605 return True;
1606
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001607 def ChromiumSyncBirthdayErrorOpHandler(self):
1608 test_name = "/chromiumsync/birthdayerror"
1609 if not self._ShouldHandleRequest(test_name):
1610 return False
1611 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1612 self.send_response(result)
1613 self.send_header('Content-Type', 'text/html')
1614 self.send_header('Content-Length', len(raw_reply))
1615 self.end_headers()
1616 self.wfile.write(raw_reply)
1617 return True;
1618
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001619 def ChromiumSyncTransientErrorOpHandler(self):
1620 test_name = "/chromiumsync/transienterror"
1621 if not self._ShouldHandleRequest(test_name):
1622 return False
1623 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1624 self.send_response(result)
1625 self.send_header('Content-Type', 'text/html')
1626 self.send_header('Content-Length', len(raw_reply))
1627 self.end_headers()
1628 self.wfile.write(raw_reply)
1629 return True;
1630
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001631 def ChromiumSyncErrorOpHandler(self):
1632 test_name = "/chromiumsync/error"
1633 if not self._ShouldHandleRequest(test_name):
1634 return False
1635 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1636 self.path)
1637 self.send_response(result)
1638 self.send_header('Content-Type', 'text/html')
1639 self.send_header('Content-Length', len(raw_reply))
1640 self.end_headers()
1641 self.wfile.write(raw_reply)
1642 return True;
1643
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001644 def ChromiumSyncSyncTabsOpHandler(self):
1645 test_name = "/chromiumsync/synctabs"
1646 if not self._ShouldHandleRequest(test_name):
1647 return False
1648 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1649 self.send_response(result)
1650 self.send_header('Content-Type', 'text/html')
1651 self.send_header('Content-Length', len(raw_reply))
1652 self.end_headers()
1653 self.wfile.write(raw_reply)
1654 return True;
1655
akalin@chromium.org154bb132010-11-12 02:20:27 +00001656
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001657def MakeDataDir():
1658 if options.data_dir:
1659 if not os.path.isdir(options.data_dir):
1660 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1661 return None
1662 my_data_dir = options.data_dir
1663 else:
1664 # Create the default path to our data dir, relative to the exe dir.
1665 my_data_dir = os.path.dirname(sys.argv[0])
1666 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001667 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001668
1669 #TODO(ibrar): Must use Find* funtion defined in google\tools
1670 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1671
1672 return my_data_dir
1673
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001674
1675class TCPEchoHandler(SocketServer.BaseRequestHandler):
1676 """The RequestHandler class for TCP echo server.
1677
1678 It is instantiated once per connection to the server, and overrides the
1679 handle() method to implement communication to the client.
1680 """
1681
1682 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001683 """Handles the request from the client and constructs a response."""
1684
1685 data = self.request.recv(65536).strip()
1686 # Verify the "echo request" message received from the client. Send back
1687 # "echo response" message if "echo request" message is valid.
1688 try:
1689 return_data = echo_message.GetEchoResponseData(data)
1690 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001691 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001692 except ValueError:
1693 return
1694
1695 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001696
1697
1698class UDPEchoHandler(SocketServer.BaseRequestHandler):
1699 """The RequestHandler class for UDP echo server.
1700
1701 It is instantiated once per connection to the server, and overrides the
1702 handle() method to implement communication to the client.
1703 """
1704
1705 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001706 """Handles the request from the client and constructs a response."""
1707
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001708 data = self.request[0].strip()
1709 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001710 # Verify the "echo request" message received from the client. Send back
1711 # "echo response" message if "echo request" message is valid.
1712 try:
1713 return_data = echo_message.GetEchoResponseData(data)
1714 if not return_data:
1715 return
1716 except ValueError:
1717 return
1718 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001719
1720
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001721class FileMultiplexer:
1722 def __init__(self, fd1, fd2) :
1723 self.__fd1 = fd1
1724 self.__fd2 = fd2
1725
1726 def __del__(self) :
1727 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1728 self.__fd1.close()
1729 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1730 self.__fd2.close()
1731
1732 def write(self, text) :
1733 self.__fd1.write(text)
1734 self.__fd2.write(text)
1735
1736 def flush(self) :
1737 self.__fd1.flush()
1738 self.__fd2.flush()
1739
initial.commit94958cf2008-07-26 22:42:52 +00001740def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001741 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001742 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001743 if options.log_to_console:
1744 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1745 else:
1746 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001747
1748 port = options.port
1749
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001750 server_data = {}
1751
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001752 if options.server_type == SERVER_HTTP:
1753 if options.cert:
1754 # let's make sure the cert file exists.
1755 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001756 print 'specified server cert file not found: ' + options.cert + \
1757 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001758 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001759 for ca_cert in options.ssl_client_ca:
1760 if not os.path.isfile(ca_cert):
1761 print 'specified trusted client CA file not found: ' + ca_cert + \
1762 ' exiting...'
1763 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001764 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001765 options.ssl_client_auth, options.ssl_client_ca,
1766 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001767 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001768 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001769 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001770 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001771
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001772 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001773 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001774 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001775 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001776 server.policy_keys = options.policy_keys
1777 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001778 elif options.server_type == SERVER_SYNC:
1779 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1780 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001781 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1782 server_data['port'] = server.server_port
1783 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001784 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001785 # Used for generating the key (randomly) that encodes the "echo request"
1786 # message.
1787 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001788 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1789 print 'Echo TCP server started on port %d...' % server.server_port
1790 server_data['port'] = server.server_port
1791 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001792 # Used for generating the key (randomly) that encodes the "echo request"
1793 # message.
1794 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001795 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1796 print 'Echo UDP server started on port %d...' % server.server_port
1797 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001798 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001799 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001800 my_data_dir = MakeDataDir()
1801
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001802 # Instantiate a dummy authorizer for managing 'virtual' users
1803 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1804
1805 # Define a new user having full r/w permissions and a read-only
1806 # anonymous user
1807 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1808
1809 authorizer.add_anonymous(my_data_dir)
1810
1811 # Instantiate FTP handler class
1812 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1813 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001814
1815 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001816 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1817 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001818
1819 # Instantiate FTP server class and listen to 127.0.0.1:port
1820 address = ('127.0.0.1', port)
1821 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001822 server_data['port'] = server.socket.getsockname()[1]
1823 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001824
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001825 # Notify the parent that we've started. (BaseServer subclasses
1826 # bind their sockets on construction.)
1827 if options.startup_pipe is not None:
akalin@chromium.org73b7b5c2010-11-26 19:42:26 +00001828 server_data_json = simplejson.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001829 server_data_len = len(server_data_json)
1830 print 'sending server_data: %s (%d bytes)' % (
1831 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001832 if sys.platform == 'win32':
1833 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1834 else:
1835 fd = options.startup_pipe
1836 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001837 # First write the data length as an unsigned 4-byte value. This
1838 # is _not_ using network byte ordering since the other end of the
1839 # pipe is on the same machine.
1840 startup_pipe.write(struct.pack('=L', server_data_len))
1841 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001842 startup_pipe.close()
1843
initial.commit94958cf2008-07-26 22:42:52 +00001844 try:
1845 server.serve_forever()
1846 except KeyboardInterrupt:
1847 print 'shutting down server'
1848 server.stop = True
1849
1850if __name__ == '__main__':
1851 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001852 option_parser.add_option("-f", '--ftp', action='store_const',
1853 const=SERVER_FTP, default=SERVER_HTTP,
1854 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001855 help='start up an FTP server.')
1856 option_parser.add_option('', '--sync', action='store_const',
1857 const=SERVER_SYNC, default=SERVER_HTTP,
1858 dest='server_type',
1859 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001860 option_parser.add_option('', '--tcp-echo', action='store_const',
1861 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1862 dest='server_type',
1863 help='start up a tcp echo server.')
1864 option_parser.add_option('', '--udp-echo', action='store_const',
1865 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1866 dest='server_type',
1867 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001868 option_parser.add_option('', '--log-to-console', action='store_const',
1869 const=True, default=False,
1870 dest='log_to_console',
1871 help='Enables or disables sys.stdout logging to '
1872 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001873 option_parser.add_option('', '--port', default='0', type='int',
1874 help='Port used by the server. If unspecified, the '
1875 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001876 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001877 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001878 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001879 help='Specify that https should be used, specify '
1880 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001881 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001882 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1883 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001884 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1885 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001886 'should include the CA named in the subject of '
1887 'the DER-encoded certificate contained in the '
1888 'specified file. This option may appear multiple '
1889 'times, indicating multiple CA names should be '
1890 'sent in the request.')
1891 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1892 help='Specify the bulk encryption algorithm(s)'
1893 'that will be accepted by the SSL server. Valid '
1894 'values are "aes256", "aes128", "3des", "rc4". If '
1895 'omitted, all algorithms will be used. This '
1896 'option may appear multiple times, indicating '
1897 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001898 option_parser.add_option('', '--file-root-url', default='/files/',
1899 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001900 option_parser.add_option('', '--startup-pipe', type='int',
1901 dest='startup_pipe',
1902 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001903 option_parser.add_option('', '--policy-key', action='append',
1904 dest='policy_keys',
1905 help='Specify a path to a PEM-encoded private key '
1906 'to use for policy signing. May be specified '
1907 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001908 'the server. If ther server has multiple keys, it '
1909 'will rotate through them in at each request a '
1910 'round-robin fashion. The server will generate a '
1911 'random key if none is specified on the command '
1912 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001913 option_parser.add_option('', '--policy-user', default='user@example.com',
1914 dest='policy_user',
1915 help='Specify the user name the server should '
1916 'report back to the client as the user owning the '
1917 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001918 options, args = option_parser.parse_args()
1919
1920 sys.exit(main(options, args))