blob: 4e27482b2dc705b971485686c123e90236e0f163 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
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
initial.commit94958cf2008-07-26 22:42:52 +000026import SocketServer
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import socket
initial.commit94958cf2008-07-26 22:42:52 +000028import sys
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000029import struct
initial.commit94958cf2008-07-26 22:42:52 +000030import time
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000031import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000032import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000033import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000034
35# Ignore deprecation warnings, they make our output more cluttered.
36warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000037
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000038import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000039import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000040import tlslite
41import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000043try:
44 import hashlib
45 _new_md5 = hashlib.md5
46except ImportError:
47 import md5
48 _new_md5 = md5.new
49
dpranke@chromium.org70049b72011-10-14 00:38:18 +000050try:
51 import json
52except ImportError:
53 import simplejson as json
54
davidben@chromium.org06fcf202010-09-22 18:15:23 +000055if sys.platform == 'win32':
56 import msvcrt
57
maruel@chromium.org756cf982009-03-05 12:46:38 +000058SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000059SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000060SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000061SERVER_TCP_ECHO = 3
62SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000063
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000064# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000065debug_output = sys.stderr
66def debug(str):
67 debug_output.write(str + "\n")
68 debug_output.flush()
69
70class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
71 """This is a specialization of of BaseHTTPServer to allow it
72 to be exited cleanly (by setting its "stop" member to True)."""
73
74 def serve_forever(self):
75 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000076 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000077 while not self.stop:
78 self.handle_request()
79 self.socket.close()
80
81class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
82 """This is a specialization of StoppableHTTPerver that add https support."""
83
davidben@chromium.org31282a12010-08-07 01:10:02 +000084 def __init__(self, server_address, request_hander_class, cert_path,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000085 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers):
initial.commit94958cf2008-07-26 22:42:52 +000086 s = open(cert_path).read()
87 x509 = tlslite.api.X509()
88 x509.parse(s)
89 self.cert_chain = tlslite.api.X509CertChain([x509])
90 s = open(cert_path).read()
91 self.private_key = tlslite.api.parsePEMKey(s, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +000092 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +000093 self.ssl_client_cas = []
94 for ca_file in ssl_client_cas:
95 s = open(ca_file).read()
96 x509 = tlslite.api.X509()
97 x509.parse(s)
98 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +000099 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
100 if ssl_bulk_ciphers is not None:
101 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000102
103 self.session_cache = tlslite.api.SessionCache()
104 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
105
106 def handshake(self, tlsConnection):
107 """Creates the SSL connection."""
108 try:
109 tlsConnection.handshakeServer(certChain=self.cert_chain,
110 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000111 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000112 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000113 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000114 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000115 tlsConnection.ignoreAbruptClose = True
116 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000117 except tlslite.api.TLSAbruptCloseError:
118 # Ignore abrupt close.
119 return True
initial.commit94958cf2008-07-26 22:42:52 +0000120 except tlslite.api.TLSError, error:
121 print "Handshake failure:", str(error)
122 return False
123
akalin@chromium.org154bb132010-11-12 02:20:27 +0000124
125class SyncHTTPServer(StoppableHTTPServer):
126 """An HTTP server that handles sync commands."""
127
128 def __init__(self, server_address, request_handler_class):
129 # We import here to avoid pulling in chromiumsync's dependencies
130 # unless strictly necessary.
131 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000132 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000133 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000134 self._sync_handler = chromiumsync.TestServer()
135 self._xmpp_socket_map = {}
136 self._xmpp_server = xmppserver.XmppServer(
137 self._xmpp_socket_map, ('localhost', 0))
138 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000139 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000140
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000141 def GetXmppServer(self):
142 return self._xmpp_server
143
akalin@chromium.org154bb132010-11-12 02:20:27 +0000144 def HandleCommand(self, query, raw_request):
145 return self._sync_handler.HandleCommand(query, raw_request)
146
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000147 def HandleRequestNoBlock(self):
148 """Handles a single request.
149
150 Copied from SocketServer._handle_request_noblock().
151 """
152 try:
153 request, client_address = self.get_request()
154 except socket.error:
155 return
156 if self.verify_request(request, client_address):
157 try:
158 self.process_request(request, client_address)
159 except:
160 self.handle_error(request, client_address)
161 self.close_request(request)
162
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000163 def SetAuthenticated(self, auth_valid):
164 self.authenticated = auth_valid
165
166 def GetAuthenticated(self):
167 return self.authenticated
168
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000169 def serve_forever(self):
170 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
171 """
172
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000173 def HandleXmppSocket(fd, socket_map, handler):
174 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000175
176 Adapted from asyncore.read() et al.
177 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000178 xmpp_connection = socket_map.get(fd)
179 # This could happen if a previous handler call caused fd to get
180 # removed from socket_map.
181 if xmpp_connection is None:
182 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000183 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000184 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000185 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
186 raise
187 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000188 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000189
190 while True:
191 read_fds = [ self.fileno() ]
192 write_fds = []
193 exceptional_fds = []
194
195 for fd, xmpp_connection in self._xmpp_socket_map.items():
196 is_r = xmpp_connection.readable()
197 is_w = xmpp_connection.writable()
198 if is_r:
199 read_fds.append(fd)
200 if is_w:
201 write_fds.append(fd)
202 if is_r or is_w:
203 exceptional_fds.append(fd)
204
205 try:
206 read_fds, write_fds, exceptional_fds = (
207 select.select(read_fds, write_fds, exceptional_fds))
208 except select.error, err:
209 if err.args[0] != errno.EINTR:
210 raise
211 else:
212 continue
213
214 for fd in read_fds:
215 if fd == self.fileno():
216 self.HandleRequestNoBlock()
217 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000218 HandleXmppSocket(fd, self._xmpp_socket_map,
219 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000220
221 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000222 HandleXmppSocket(fd, self._xmpp_socket_map,
223 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000224
225 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000226 HandleXmppSocket(fd, self._xmpp_socket_map,
227 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000228
akalin@chromium.org154bb132010-11-12 02:20:27 +0000229
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000230class TCPEchoServer(SocketServer.TCPServer):
231 """A TCP echo server that echoes back what it has received."""
232
233 def server_bind(self):
234 """Override server_bind to store the server name."""
235 SocketServer.TCPServer.server_bind(self)
236 host, port = self.socket.getsockname()[:2]
237 self.server_name = socket.getfqdn(host)
238 self.server_port = port
239
240 def serve_forever(self):
241 self.stop = False
242 self.nonce_time = None
243 while not self.stop:
244 self.handle_request()
245 self.socket.close()
246
247
248class UDPEchoServer(SocketServer.UDPServer):
249 """A UDP echo server that echoes back what it has received."""
250
251 def server_bind(self):
252 """Override server_bind to store the server name."""
253 SocketServer.UDPServer.server_bind(self)
254 host, port = self.socket.getsockname()[:2]
255 self.server_name = socket.getfqdn(host)
256 self.server_port = port
257
258 def serve_forever(self):
259 self.stop = False
260 self.nonce_time = None
261 while not self.stop:
262 self.handle_request()
263 self.socket.close()
264
265
akalin@chromium.org154bb132010-11-12 02:20:27 +0000266class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
267
268 def __init__(self, request, client_address, socket_server,
269 connect_handlers, get_handlers, post_handlers, put_handlers):
270 self._connect_handlers = connect_handlers
271 self._get_handlers = get_handlers
272 self._post_handlers = post_handlers
273 self._put_handlers = put_handlers
274 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
275 self, request, client_address, socket_server)
276
277 def log_request(self, *args, **kwargs):
278 # Disable request logging to declutter test log output.
279 pass
280
281 def _ShouldHandleRequest(self, handler_name):
282 """Determines if the path can be handled by the handler.
283
284 We consider a handler valid if the path begins with the
285 handler name. It can optionally be followed by "?*", "/*".
286 """
287
288 pattern = re.compile('%s($|\?|/).*' % handler_name)
289 return pattern.match(self.path)
290
291 def do_CONNECT(self):
292 for handler in self._connect_handlers:
293 if handler():
294 return
295
296 def do_GET(self):
297 for handler in self._get_handlers:
298 if handler():
299 return
300
301 def do_POST(self):
302 for handler in self._post_handlers:
303 if handler():
304 return
305
306 def do_PUT(self):
307 for handler in self._put_handlers:
308 if handler():
309 return
310
311
312class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000313
314 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000315 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000316 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000317 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000318 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000319 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000320 self.NoCacheMaxAgeTimeHandler,
321 self.NoCacheTimeHandler,
322 self.CacheTimeHandler,
323 self.CacheExpiresHandler,
324 self.CacheProxyRevalidateHandler,
325 self.CachePrivateHandler,
326 self.CachePublicHandler,
327 self.CacheSMaxAgeHandler,
328 self.CacheMustRevalidateHandler,
329 self.CacheMustRevalidateMaxAgeHandler,
330 self.CacheNoStoreHandler,
331 self.CacheNoStoreMaxAgeHandler,
332 self.CacheNoTransformHandler,
333 self.DownloadHandler,
334 self.DownloadFinishHandler,
335 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000336 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000337 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000338 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000339 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000340 self.SetCookieHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000341 self.AuthBasicHandler,
342 self.AuthDigestHandler,
343 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000344 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000345 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000346 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000347 self.ServerRedirectHandler,
348 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000349 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000350 self.MultipartSlowHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000351 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000353 self.EchoTitleHandler,
354 self.EchoAllHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000355 self.EchoHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000356 self.DeviceManagementHandler] + get_handlers
357 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000358 self.EchoTitleHandler,
359 self.EchoAllHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000360 self.EchoHandler] + get_handlers
initial.commit94958cf2008-07-26 22:42:52 +0000361
maruel@google.come250a9b2009-03-10 17:39:46 +0000362 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000363 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000364 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 'gif': 'image/gif',
366 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000367 'jpg' : 'image/jpeg',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000368 'pdf' : 'application/pdf',
369 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000370 }
initial.commit94958cf2008-07-26 22:42:52 +0000371 self._default_mime_type = 'text/html'
372
akalin@chromium.org154bb132010-11-12 02:20:27 +0000373 BasePageHandler.__init__(self, request, client_address, socket_server,
374 connect_handlers, get_handlers, post_handlers,
375 put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000376
initial.commit94958cf2008-07-26 22:42:52 +0000377 def GetMIMETypeFromName(self, file_name):
378 """Returns the mime type for the specified file_name. So far it only looks
379 at the file extension."""
380
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000381 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000382 if len(extension) == 0:
383 # no extension.
384 return self._default_mime_type
385
ericroman@google.comc17ca532009-05-07 03:51:05 +0000386 # extension starts with a dot, so we need to remove it
387 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000388
initial.commit94958cf2008-07-26 22:42:52 +0000389 def NoCacheMaxAgeTimeHandler(self):
390 """This request handler yields a page with the title set to the current
391 system time, and no caching requested."""
392
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000393 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000394 return False
395
396 self.send_response(200)
397 self.send_header('Cache-Control', 'max-age=0')
398 self.send_header('Content-type', 'text/html')
399 self.end_headers()
400
maruel@google.come250a9b2009-03-10 17:39:46 +0000401 self.wfile.write('<html><head><title>%s</title></head></html>' %
402 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000403
404 return True
405
406 def NoCacheTimeHandler(self):
407 """This request handler yields a page with the title set to the current
408 system time, and no caching requested."""
409
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000410 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000411 return False
412
413 self.send_response(200)
414 self.send_header('Cache-Control', 'no-cache')
415 self.send_header('Content-type', 'text/html')
416 self.end_headers()
417
maruel@google.come250a9b2009-03-10 17:39:46 +0000418 self.wfile.write('<html><head><title>%s</title></head></html>' %
419 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000420
421 return True
422
423 def CacheTimeHandler(self):
424 """This request handler yields a page with the title set to the current
425 system time, and allows caching for one minute."""
426
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000427 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000428 return False
429
430 self.send_response(200)
431 self.send_header('Cache-Control', 'max-age=60')
432 self.send_header('Content-type', 'text/html')
433 self.end_headers()
434
maruel@google.come250a9b2009-03-10 17:39:46 +0000435 self.wfile.write('<html><head><title>%s</title></head></html>' %
436 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000437
438 return True
439
440 def CacheExpiresHandler(self):
441 """This request handler yields a page with the title set to the current
442 system time, and set the page to expire on 1 Jan 2099."""
443
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000444 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000445 return False
446
447 self.send_response(200)
448 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
449 self.send_header('Content-type', 'text/html')
450 self.end_headers()
451
maruel@google.come250a9b2009-03-10 17:39:46 +0000452 self.wfile.write('<html><head><title>%s</title></head></html>' %
453 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000454
455 return True
456
457 def CacheProxyRevalidateHandler(self):
458 """This request handler yields a page with the title set to the current
459 system time, and allows caching for 60 seconds"""
460
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000461 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000462 return False
463
464 self.send_response(200)
465 self.send_header('Content-type', 'text/html')
466 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
467 self.end_headers()
468
maruel@google.come250a9b2009-03-10 17:39:46 +0000469 self.wfile.write('<html><head><title>%s</title></head></html>' %
470 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000471
472 return True
473
474 def CachePrivateHandler(self):
475 """This request handler yields a page with the title set to the current
476 system time, and allows caching for 5 seconds."""
477
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000478 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000479 return False
480
481 self.send_response(200)
482 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000483 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000484 self.end_headers()
485
maruel@google.come250a9b2009-03-10 17:39:46 +0000486 self.wfile.write('<html><head><title>%s</title></head></html>' %
487 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000488
489 return True
490
491 def CachePublicHandler(self):
492 """This request handler yields a page with the title set to the current
493 system time, and allows caching for 5 seconds."""
494
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000495 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000496 return False
497
498 self.send_response(200)
499 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000500 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000501 self.end_headers()
502
maruel@google.come250a9b2009-03-10 17:39:46 +0000503 self.wfile.write('<html><head><title>%s</title></head></html>' %
504 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000505
506 return True
507
508 def CacheSMaxAgeHandler(self):
509 """This request handler yields a page with the title set to the current
510 system time, and does not allow for caching."""
511
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000512 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000513 return False
514
515 self.send_response(200)
516 self.send_header('Content-type', 'text/html')
517 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
518 self.end_headers()
519
maruel@google.come250a9b2009-03-10 17:39:46 +0000520 self.wfile.write('<html><head><title>%s</title></head></html>' %
521 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000522
523 return True
524
525 def CacheMustRevalidateHandler(self):
526 """This request handler yields a page with the title set to the current
527 system time, and does not allow caching."""
528
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000529 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000530 return False
531
532 self.send_response(200)
533 self.send_header('Content-type', 'text/html')
534 self.send_header('Cache-Control', 'must-revalidate')
535 self.end_headers()
536
maruel@google.come250a9b2009-03-10 17:39:46 +0000537 self.wfile.write('<html><head><title>%s</title></head></html>' %
538 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000539
540 return True
541
542 def CacheMustRevalidateMaxAgeHandler(self):
543 """This request handler yields a page with the title set to the current
544 system time, and does not allow caching event though max-age of 60
545 seconds is specified."""
546
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000547 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000548 return False
549
550 self.send_response(200)
551 self.send_header('Content-type', 'text/html')
552 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
553 self.end_headers()
554
maruel@google.come250a9b2009-03-10 17:39:46 +0000555 self.wfile.write('<html><head><title>%s</title></head></html>' %
556 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000557
558 return True
559
initial.commit94958cf2008-07-26 22:42:52 +0000560 def CacheNoStoreHandler(self):
561 """This request handler yields a page with the title set to the current
562 system time, and does not allow the page to be stored."""
563
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000564 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000565 return False
566
567 self.send_response(200)
568 self.send_header('Content-type', 'text/html')
569 self.send_header('Cache-Control', 'no-store')
570 self.end_headers()
571
maruel@google.come250a9b2009-03-10 17:39:46 +0000572 self.wfile.write('<html><head><title>%s</title></head></html>' %
573 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000574
575 return True
576
577 def CacheNoStoreMaxAgeHandler(self):
578 """This request handler yields a page with the title set to the current
579 system time, and does not allow the page to be stored even though max-age
580 of 60 seconds is specified."""
581
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000582 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000583 return False
584
585 self.send_response(200)
586 self.send_header('Content-type', 'text/html')
587 self.send_header('Cache-Control', 'max-age=60, no-store')
588 self.end_headers()
589
maruel@google.come250a9b2009-03-10 17:39:46 +0000590 self.wfile.write('<html><head><title>%s</title></head></html>' %
591 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000592
593 return True
594
595
596 def CacheNoTransformHandler(self):
597 """This request handler yields a page with the title set to the current
598 system time, and does not allow the content to transformed during
599 user-agent caching"""
600
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000601 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000602 return False
603
604 self.send_response(200)
605 self.send_header('Content-type', 'text/html')
606 self.send_header('Cache-Control', 'no-transform')
607 self.end_headers()
608
maruel@google.come250a9b2009-03-10 17:39:46 +0000609 self.wfile.write('<html><head><title>%s</title></head></html>' %
610 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000611
612 return True
613
614 def EchoHeader(self):
615 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000616 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000617
ananta@chromium.org56812d02011-04-07 17:52:05 +0000618 """This function echoes back the value of a specific request header"""
619 """while allowing caching for 16 hours."""
620 def EchoHeaderCache(self):
621 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000622
623 def EchoHeaderHelper(self, echo_header):
624 """This function echoes back the value of the request header passed in."""
625 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000626 return False
627
628 query_char = self.path.find('?')
629 if query_char != -1:
630 header_name = self.path[query_char+1:]
631
632 self.send_response(200)
633 self.send_header('Content-type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000634 if echo_header == '/echoheadercache':
635 self.send_header('Cache-control', 'max-age=60000')
636 else:
637 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000638 # insert a vary header to properly indicate that the cachability of this
639 # request is subject to value of the request header being echoed.
640 if len(header_name) > 0:
641 self.send_header('Vary', header_name)
642 self.end_headers()
643
644 if len(header_name) > 0:
645 self.wfile.write(self.headers.getheader(header_name))
646
647 return True
648
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000649 def ReadRequestBody(self):
650 """This function reads the body of the current HTTP request, handling
651 both plain and chunked transfer encoded requests."""
652
653 if self.headers.getheader('transfer-encoding') != 'chunked':
654 length = int(self.headers.getheader('content-length'))
655 return self.rfile.read(length)
656
657 # Read the request body as chunks.
658 body = ""
659 while True:
660 line = self.rfile.readline()
661 length = int(line, 16)
662 if length == 0:
663 self.rfile.readline()
664 break
665 body += self.rfile.read(length)
666 self.rfile.read(2)
667 return body
668
initial.commit94958cf2008-07-26 22:42:52 +0000669 def EchoHandler(self):
670 """This handler just echoes back the payload of the request, for testing
671 form submission."""
672
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000673 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000674 return False
675
676 self.send_response(200)
677 self.send_header('Content-type', 'text/html')
678 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000679 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000680 return True
681
682 def EchoTitleHandler(self):
683 """This handler is like Echo, but sets the page title to the request."""
684
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000685 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000686 return False
687
688 self.send_response(200)
689 self.send_header('Content-type', 'text/html')
690 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000691 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000692 self.wfile.write('<html><head><title>')
693 self.wfile.write(request)
694 self.wfile.write('</title></head></html>')
695 return True
696
697 def EchoAllHandler(self):
698 """This handler yields a (more) human-readable page listing information
699 about the request header & contents."""
700
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000701 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000702 return False
703
704 self.send_response(200)
705 self.send_header('Content-type', 'text/html')
706 self.end_headers()
707 self.wfile.write('<html><head><style>'
708 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
709 '</style></head><body>'
710 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000711 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000712 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000713
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000714 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000715 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000716 params = cgi.parse_qs(qs, keep_blank_values=1)
717
718 for param in params:
719 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000720
721 self.wfile.write('</pre>')
722
723 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
724
725 self.wfile.write('</body></html>')
726 return True
727
728 def DownloadHandler(self):
729 """This handler sends a downloadable file with or without reporting
730 the size (6K)."""
731
732 if self.path.startswith("/download-unknown-size"):
733 send_length = False
734 elif self.path.startswith("/download-known-size"):
735 send_length = True
736 else:
737 return False
738
739 #
740 # The test which uses this functionality is attempting to send
741 # small chunks of data to the client. Use a fairly large buffer
742 # so that we'll fill chrome's IO buffer enough to force it to
743 # actually write the data.
744 # See also the comments in the client-side of this test in
745 # download_uitest.cc
746 #
747 size_chunk1 = 35*1024
748 size_chunk2 = 10*1024
749
750 self.send_response(200)
751 self.send_header('Content-type', 'application/octet-stream')
752 self.send_header('Cache-Control', 'max-age=0')
753 if send_length:
754 self.send_header('Content-Length', size_chunk1 + size_chunk2)
755 self.end_headers()
756
757 # First chunk of data:
758 self.wfile.write("*" * size_chunk1)
759 self.wfile.flush()
760
761 # handle requests until one of them clears this flag.
762 self.server.waitForDownload = True
763 while self.server.waitForDownload:
764 self.server.handle_request()
765
766 # Second chunk of data:
767 self.wfile.write("*" * size_chunk2)
768 return True
769
770 def DownloadFinishHandler(self):
771 """This handler just tells the server to finish the current download."""
772
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000773 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000774 return False
775
776 self.server.waitForDownload = False
777 self.send_response(200)
778 self.send_header('Content-type', 'text/html')
779 self.send_header('Cache-Control', 'max-age=0')
780 self.end_headers()
781 return True
782
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000783 def _ReplaceFileData(self, data, query_parameters):
784 """Replaces matching substrings in a file.
785
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000786 If the 'replace_text' URL query parameter is present, it is expected to be
787 of the form old_text:new_text, which indicates that any old_text strings in
788 the file are replaced with new_text. Multiple 'replace_text' parameters may
789 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000790
791 If the parameters are not present, |data| is returned.
792 """
793 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000794 replace_text_values = query_dict.get('replace_text', [])
795 for replace_text_value in replace_text_values:
796 replace_text_args = replace_text_value.split(':')
797 if len(replace_text_args) != 2:
798 raise ValueError(
799 'replace_text must be of form old_text:new_text. Actual value: %s' %
800 replace_text_value)
801 old_text_b64, new_text_b64 = replace_text_args
802 old_text = base64.urlsafe_b64decode(old_text_b64)
803 new_text = base64.urlsafe_b64decode(new_text_b64)
804 data = data.replace(old_text, new_text)
805 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000806
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000807 def ZipFileHandler(self):
808 """This handler sends the contents of the requested file in compressed form.
809 Can pass in a parameter that specifies that the content length be
810 C - the compressed size (OK),
811 U - the uncompressed size (Non-standard, but handled),
812 S - less than compressed (OK because we keep going),
813 M - larger than compressed but less than uncompressed (an error),
814 L - larger than uncompressed (an error)
815 Example: compressedfiles/Picture_1.doc?C
816 """
817
818 prefix = "/compressedfiles/"
819 if not self.path.startswith(prefix):
820 return False
821
822 # Consume a request body if present.
823 if self.command == 'POST' or self.command == 'PUT' :
824 self.ReadRequestBody()
825
826 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
827
828 if not query in ('C', 'U', 'S', 'M', 'L'):
829 return False
830
831 sub_path = url_path[len(prefix):]
832 entries = sub_path.split('/')
833 file_path = os.path.join(self.server.data_dir, *entries)
834 if os.path.isdir(file_path):
835 file_path = os.path.join(file_path, 'index.html')
836
837 if not os.path.isfile(file_path):
838 print "File not found " + sub_path + " full path:" + file_path
839 self.send_error(404)
840 return True
841
842 f = open(file_path, "rb")
843 data = f.read()
844 uncompressed_len = len(data)
845 f.close()
846
847 # Compress the data.
848 data = zlib.compress(data)
849 compressed_len = len(data)
850
851 content_length = compressed_len
852 if query == 'U':
853 content_length = uncompressed_len
854 elif query == 'S':
855 content_length = compressed_len / 2
856 elif query == 'M':
857 content_length = (compressed_len + uncompressed_len) / 2
858 elif query == 'L':
859 content_length = compressed_len + uncompressed_len
860
861 self.send_response(200)
862 self.send_header('Content-type', 'application/msword')
863 self.send_header('Content-encoding', 'deflate')
864 self.send_header('Connection', 'close')
865 self.send_header('Content-Length', content_length)
866 self.send_header('ETag', '\'' + file_path + '\'')
867 self.end_headers()
868
869 self.wfile.write(data)
870
871 return True
872
initial.commit94958cf2008-07-26 22:42:52 +0000873 def FileHandler(self):
874 """This handler sends the contents of the requested file. Wow, it's like
875 a real webserver!"""
876
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000877 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000878 if not self.path.startswith(prefix):
879 return False
880
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000881 # Consume a request body if present.
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000882 if self.command == 'POST' or self.command == 'PUT' :
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000883 self.ReadRequestBody()
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000884
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000885 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
886 sub_path = url_path[len(prefix):]
887 entries = sub_path.split('/')
888 file_path = os.path.join(self.server.data_dir, *entries)
889 if os.path.isdir(file_path):
890 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000891
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000892 if not os.path.isfile(file_path):
893 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000894 self.send_error(404)
895 return True
896
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000897 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000898 data = f.read()
899 f.close()
900
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000901 data = self._ReplaceFileData(data, query)
902
initial.commit94958cf2008-07-26 22:42:52 +0000903 # If file.mock-http-headers exists, it contains the headers we
904 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000905 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000906 if os.path.isfile(headers_path):
907 f = open(headers_path, "r")
908
909 # "HTTP/1.1 200 OK"
910 response = f.readline()
911 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
912 self.send_response(int(status_code))
913
914 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000915 header_values = re.findall('(\S+):\s*(.*)', line)
916 if len(header_values) > 0:
917 # "name: value"
918 name, value = header_values[0]
919 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000920 f.close()
921 else:
922 # Could be more generic once we support mime-type sniffing, but for
923 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000924
925 range = self.headers.get('Range')
926 if range and range.startswith('bytes='):
927 # Note this doesn't handle all valid byte range values (i.e. open ended
928 # ones), just enough for what we needed so far.
929 range = range[6:].split('-')
930 start = int(range[0])
931 end = int(range[1])
932
933 self.send_response(206)
934 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
935 str(len(data))
936 self.send_header('Content-Range', content_range)
937 data = data[start: end + 1]
938 else:
939 self.send_response(200)
940
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000941 self.send_header('Content-type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000942 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000943 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000944 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000945 self.end_headers()
946
947 self.wfile.write(data)
948
949 return True
950
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000951 def SetCookieHandler(self):
952 """This handler just sets a cookie, for testing cookie handling."""
953
954 if not self._ShouldHandleRequest("/set-cookie"):
955 return False
956
957 query_char = self.path.find('?')
958 if query_char != -1:
959 cookie_values = self.path[query_char + 1:].split('&')
960 else:
961 cookie_values = ("",)
962 self.send_response(200)
963 self.send_header('Content-type', 'text/html')
964 for cookie_value in cookie_values:
965 self.send_header('Set-Cookie', '%s' % cookie_value)
966 self.end_headers()
967 for cookie_value in cookie_values:
968 self.wfile.write('%s' % cookie_value)
969 return True
970
initial.commit94958cf2008-07-26 22:42:52 +0000971 def AuthBasicHandler(self):
972 """This handler tests 'Basic' authentication. It just sends a page with
973 title 'user/pass' if you succeed."""
974
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000975 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000976 return False
977
978 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000979 expected_password = 'secret'
980 realm = 'testrealm'
981 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +0000982
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000983 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
984 query_params = cgi.parse_qs(query, True)
985 if 'set-cookie-if-challenged' in query_params:
986 set_cookie_if_challenged = True
987 if 'password' in query_params:
988 expected_password = query_params['password'][0]
989 if 'realm' in query_params:
990 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +0000991
initial.commit94958cf2008-07-26 22:42:52 +0000992 auth = self.headers.getheader('authorization')
993 try:
994 if not auth:
995 raise Exception('no auth')
996 b64str = re.findall(r'Basic (\S+)', auth)[0]
997 userpass = base64.b64decode(b64str)
998 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +0000999 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001000 raise Exception('wrong password')
1001 except Exception, e:
1002 # Authentication failed.
1003 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001004 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
initial.commit94958cf2008-07-26 22:42:52 +00001005 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001006 if set_cookie_if_challenged:
1007 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001008 self.end_headers()
1009 self.wfile.write('<html><head>')
1010 self.wfile.write('<title>Denied: %s</title>' % e)
1011 self.wfile.write('</head><body>')
1012 self.wfile.write('auth=%s<p>' % auth)
1013 self.wfile.write('b64str=%s<p>' % b64str)
1014 self.wfile.write('username: %s<p>' % username)
1015 self.wfile.write('userpass: %s<p>' % userpass)
1016 self.wfile.write('password: %s<p>' % password)
1017 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1018 self.wfile.write('</body></html>')
1019 return True
1020
1021 # Authentication successful. (Return a cachable response to allow for
1022 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001023 old_protocol_version = self.protocol_version
1024 self.protocol_version = "HTTP/1.1"
1025
initial.commit94958cf2008-07-26 22:42:52 +00001026 if_none_match = self.headers.getheader('if-none-match')
1027 if if_none_match == "abc":
1028 self.send_response(304)
1029 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001030 elif url_path.endswith(".gif"):
1031 # Using chrome/test/data/google/logo.gif as the test image
1032 test_image_path = ['google', 'logo.gif']
1033 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1034 if not os.path.isfile(gif_path):
1035 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001036 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001037 return True
1038
1039 f = open(gif_path, "rb")
1040 data = f.read()
1041 f.close()
1042
1043 self.send_response(200)
1044 self.send_header('Content-type', 'image/gif')
1045 self.send_header('Cache-control', 'max-age=60000')
1046 self.send_header('Etag', 'abc')
1047 self.end_headers()
1048 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001049 else:
1050 self.send_response(200)
1051 self.send_header('Content-type', 'text/html')
1052 self.send_header('Cache-control', 'max-age=60000')
1053 self.send_header('Etag', 'abc')
1054 self.end_headers()
1055 self.wfile.write('<html><head>')
1056 self.wfile.write('<title>%s/%s</title>' % (username, password))
1057 self.wfile.write('</head><body>')
1058 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001059 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001060 self.wfile.write('</body></html>')
1061
rvargas@google.com54453b72011-05-19 01:11:11 +00001062 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001063 return True
1064
tonyg@chromium.org75054202010-03-31 22:06:10 +00001065 def GetNonce(self, force_reset=False):
1066 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001067
tonyg@chromium.org75054202010-03-31 22:06:10 +00001068 This is a fake implementation. A real implementation would only use a given
1069 nonce a single time (hence the name n-once). However, for the purposes of
1070 unittesting, we don't care about the security of the nonce.
1071
1072 Args:
1073 force_reset: Iff set, the nonce will be changed. Useful for testing the
1074 "stale" response.
1075 """
1076 if force_reset or not self.server.nonce_time:
1077 self.server.nonce_time = time.time()
1078 return _new_md5('privatekey%s%d' %
1079 (self.path, self.server.nonce_time)).hexdigest()
1080
1081 def AuthDigestHandler(self):
1082 """This handler tests 'Digest' authentication.
1083
1084 It just sends a page with title 'user/pass' if you succeed.
1085
1086 A stale response is sent iff "stale" is present in the request path.
1087 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001088 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001089 return False
1090
tonyg@chromium.org75054202010-03-31 22:06:10 +00001091 stale = 'stale' in self.path
1092 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001093 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001094 password = 'secret'
1095 realm = 'testrealm'
1096
1097 auth = self.headers.getheader('authorization')
1098 pairs = {}
1099 try:
1100 if not auth:
1101 raise Exception('no auth')
1102 if not auth.startswith('Digest'):
1103 raise Exception('not digest')
1104 # Pull out all the name="value" pairs as a dictionary.
1105 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1106
1107 # Make sure it's all valid.
1108 if pairs['nonce'] != nonce:
1109 raise Exception('wrong nonce')
1110 if pairs['opaque'] != opaque:
1111 raise Exception('wrong opaque')
1112
1113 # Check the 'response' value and make sure it matches our magic hash.
1114 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001115 hash_a1 = _new_md5(
1116 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001117 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001118 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001119 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001120 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1121 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001122 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001123
1124 if pairs['response'] != response:
1125 raise Exception('wrong password')
1126 except Exception, e:
1127 # Authentication failed.
1128 self.send_response(401)
1129 hdr = ('Digest '
1130 'realm="%s", '
1131 'domain="/", '
1132 'qop="auth", '
1133 'algorithm=MD5, '
1134 'nonce="%s", '
1135 'opaque="%s"') % (realm, nonce, opaque)
1136 if stale:
1137 hdr += ', stale="TRUE"'
1138 self.send_header('WWW-Authenticate', hdr)
1139 self.send_header('Content-type', 'text/html')
1140 self.end_headers()
1141 self.wfile.write('<html><head>')
1142 self.wfile.write('<title>Denied: %s</title>' % e)
1143 self.wfile.write('</head><body>')
1144 self.wfile.write('auth=%s<p>' % auth)
1145 self.wfile.write('pairs=%s<p>' % pairs)
1146 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1147 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1148 self.wfile.write('</body></html>')
1149 return True
1150
1151 # Authentication successful.
1152 self.send_response(200)
1153 self.send_header('Content-type', 'text/html')
1154 self.end_headers()
1155 self.wfile.write('<html><head>')
1156 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1157 self.wfile.write('</head><body>')
1158 self.wfile.write('auth=%s<p>' % auth)
1159 self.wfile.write('pairs=%s<p>' % pairs)
1160 self.wfile.write('</body></html>')
1161
1162 return True
1163
1164 def SlowServerHandler(self):
1165 """Wait for the user suggested time before responding. The syntax is
1166 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001167 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001168 return False
1169 query_char = self.path.find('?')
1170 wait_sec = 1.0
1171 if query_char >= 0:
1172 try:
1173 wait_sec = int(self.path[query_char + 1:])
1174 except ValueError:
1175 pass
1176 time.sleep(wait_sec)
1177 self.send_response(200)
1178 self.send_header('Content-type', 'text/plain')
1179 self.end_headers()
1180 self.wfile.write("waited %d seconds" % wait_sec)
1181 return True
1182
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001183 def ChunkedServerHandler(self):
1184 """Send chunked response. Allows to specify chunks parameters:
1185 - waitBeforeHeaders - ms to wait before sending headers
1186 - waitBetweenChunks - ms to wait between chunks
1187 - chunkSize - size of each chunk in bytes
1188 - chunksNumber - number of chunks
1189 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1190 waits one second, then sends headers and five chunks five bytes each."""
1191 if not self._ShouldHandleRequest("/chunked"):
1192 return False
1193 query_char = self.path.find('?')
1194 chunkedSettings = {'waitBeforeHeaders' : 0,
1195 'waitBetweenChunks' : 0,
1196 'chunkSize' : 5,
1197 'chunksNumber' : 5}
1198 if query_char >= 0:
1199 params = self.path[query_char + 1:].split('&')
1200 for param in params:
1201 keyValue = param.split('=')
1202 if len(keyValue) == 2:
1203 try:
1204 chunkedSettings[keyValue[0]] = int(keyValue[1])
1205 except ValueError:
1206 pass
1207 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1208 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1209 self.send_response(200)
1210 self.send_header('Content-type', 'text/plain')
1211 self.send_header('Connection', 'close')
1212 self.send_header('Transfer-Encoding', 'chunked')
1213 self.end_headers()
1214 # Chunked encoding: sending all chunks, then final zero-length chunk and
1215 # then final CRLF.
1216 for i in range(0, chunkedSettings['chunksNumber']):
1217 if i > 0:
1218 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1219 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1220 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1221 self.sendChunkHelp('')
1222 return True
1223
initial.commit94958cf2008-07-26 22:42:52 +00001224 def ContentTypeHandler(self):
1225 """Returns a string of html with the given content type. E.g.,
1226 /contenttype?text/css returns an html file with the Content-Type
1227 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001228 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001229 return False
1230 query_char = self.path.find('?')
1231 content_type = self.path[query_char + 1:].strip()
1232 if not content_type:
1233 content_type = 'text/html'
1234 self.send_response(200)
1235 self.send_header('Content-Type', content_type)
1236 self.end_headers()
1237 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1238 return True
1239
creis@google.com2f4f6a42011-03-25 19:44:19 +00001240 def NoContentHandler(self):
1241 """Returns a 204 No Content response."""
1242 if not self._ShouldHandleRequest("/nocontent"):
1243 return False
1244 self.send_response(204)
1245 self.end_headers()
1246 return True
1247
initial.commit94958cf2008-07-26 22:42:52 +00001248 def ServerRedirectHandler(self):
1249 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001250 '/server-redirect?http://foo.bar/asdf' to redirect to
1251 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001252
1253 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001254 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001255 return False
1256
1257 query_char = self.path.find('?')
1258 if query_char < 0 or len(self.path) <= query_char + 1:
1259 self.sendRedirectHelp(test_name)
1260 return True
1261 dest = self.path[query_char + 1:]
1262
1263 self.send_response(301) # moved permanently
1264 self.send_header('Location', dest)
1265 self.send_header('Content-type', 'text/html')
1266 self.end_headers()
1267 self.wfile.write('<html><head>')
1268 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1269
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001270 return True
initial.commit94958cf2008-07-26 22:42:52 +00001271
1272 def ClientRedirectHandler(self):
1273 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001274 '/client-redirect?http://foo.bar/asdf' to redirect to
1275 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001276
1277 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001278 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001279 return False
1280
1281 query_char = self.path.find('?');
1282 if query_char < 0 or len(self.path) <= query_char + 1:
1283 self.sendRedirectHelp(test_name)
1284 return True
1285 dest = self.path[query_char + 1:]
1286
1287 self.send_response(200)
1288 self.send_header('Content-type', 'text/html')
1289 self.end_headers()
1290 self.wfile.write('<html><head>')
1291 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1292 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1293
1294 return True
1295
tony@chromium.org03266982010-03-05 03:18:42 +00001296 def MultipartHandler(self):
1297 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001298 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001299 if not self._ShouldHandleRequest(test_name):
1300 return False
1301
1302 num_frames = 10
1303 bound = '12345'
1304 self.send_response(200)
1305 self.send_header('Content-type',
1306 'multipart/x-mixed-replace;boundary=' + bound)
1307 self.end_headers()
1308
1309 for i in xrange(num_frames):
1310 self.wfile.write('--' + bound + '\r\n')
1311 self.wfile.write('Content-type: text/html\r\n\r\n')
1312 self.wfile.write('<title>page ' + str(i) + '</title>')
1313 self.wfile.write('page ' + str(i))
1314
1315 self.wfile.write('--' + bound + '--')
1316 return True
1317
tony@chromium.org4cb88302011-09-27 22:13:49 +00001318 def MultipartSlowHandler(self):
1319 """Send a multipart response (3 text/html pages) with a slight delay
1320 between each page. This is similar to how some pages show status using
1321 multipart."""
1322 test_name = '/multipart-slow'
1323 if not self._ShouldHandleRequest(test_name):
1324 return False
1325
1326 num_frames = 3
1327 bound = '12345'
1328 self.send_response(200)
1329 self.send_header('Content-type',
1330 'multipart/x-mixed-replace;boundary=' + bound)
1331 self.end_headers()
1332
1333 for i in xrange(num_frames):
1334 self.wfile.write('--' + bound + '\r\n')
1335 self.wfile.write('Content-type: text/html\r\n\r\n')
1336 time.sleep(0.25)
1337 if i == 2:
1338 self.wfile.write('<title>PASS</title>')
1339 else:
1340 self.wfile.write('<title>page ' + str(i) + '</title>')
1341 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1342
1343 self.wfile.write('--' + bound + '--')
1344 return True
1345
initial.commit94958cf2008-07-26 22:42:52 +00001346 def DefaultResponseHandler(self):
1347 """This is the catch-all response handler for requests that aren't handled
1348 by one of the special handlers above.
1349 Note that we specify the content-length as without it the https connection
1350 is not closed properly (and the browser keeps expecting data)."""
1351
1352 contents = "Default response given for path: " + self.path
1353 self.send_response(200)
1354 self.send_header('Content-type', 'text/html')
1355 self.send_header("Content-Length", len(contents))
1356 self.end_headers()
1357 self.wfile.write(contents)
1358 return True
1359
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001360 def RedirectConnectHandler(self):
1361 """Sends a redirect to the CONNECT request for www.redirect.com. This
1362 response is not specified by the RFC, so the browser should not follow
1363 the redirect."""
1364
1365 if (self.path.find("www.redirect.com") < 0):
1366 return False
1367
1368 dest = "http://www.destination.com/foo.js"
1369
1370 self.send_response(302) # moved temporarily
1371 self.send_header('Location', dest)
1372 self.send_header('Connection', 'close')
1373 self.end_headers()
1374 return True
1375
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001376 def ServerAuthConnectHandler(self):
1377 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1378 response doesn't make sense because the proxy server cannot request
1379 server authentication."""
1380
1381 if (self.path.find("www.server-auth.com") < 0):
1382 return False
1383
1384 challenge = 'Basic realm="WallyWorld"'
1385
1386 self.send_response(401) # unauthorized
1387 self.send_header('WWW-Authenticate', challenge)
1388 self.send_header('Connection', 'close')
1389 self.end_headers()
1390 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001391
1392 def DefaultConnectResponseHandler(self):
1393 """This is the catch-all response handler for CONNECT requests that aren't
1394 handled by one of the special handlers above. Real Web servers respond
1395 with 400 to CONNECT requests."""
1396
1397 contents = "Your client has issued a malformed or illegal request."
1398 self.send_response(400) # bad request
1399 self.send_header('Content-type', 'text/html')
1400 self.send_header("Content-Length", len(contents))
1401 self.end_headers()
1402 self.wfile.write(contents)
1403 return True
1404
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001405 def DeviceManagementHandler(self):
1406 """Delegates to the device management service used for cloud policy."""
1407 if not self._ShouldHandleRequest("/device_management"):
1408 return False
1409
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001410 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001411
1412 if not self.server._device_management_handler:
1413 import device_management
1414 policy_path = os.path.join(self.server.data_dir, 'device_management')
1415 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001416 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001417 self.server.policy_keys,
1418 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001419
1420 http_response, raw_reply = (
1421 self.server._device_management_handler.HandleRequest(self.path,
1422 self.headers,
1423 raw_request))
1424 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001425 if (http_response == 200):
1426 self.send_header('Content-type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001427 self.end_headers()
1428 self.wfile.write(raw_reply)
1429 return True
1430
initial.commit94958cf2008-07-26 22:42:52 +00001431 # called by the redirect handling function when there is no parameter
1432 def sendRedirectHelp(self, redirect_name):
1433 self.send_response(200)
1434 self.send_header('Content-type', 'text/html')
1435 self.end_headers()
1436 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1437 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1438 self.wfile.write('</body></html>')
1439
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001440 # called by chunked handling function
1441 def sendChunkHelp(self, chunk):
1442 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1443 self.wfile.write('%X\r\n' % len(chunk))
1444 self.wfile.write(chunk)
1445 self.wfile.write('\r\n')
1446
akalin@chromium.org154bb132010-11-12 02:20:27 +00001447
1448class SyncPageHandler(BasePageHandler):
1449 """Handler for the main HTTP sync server."""
1450
1451 def __init__(self, request, client_address, sync_http_server):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001452 get_handlers = [self.ChromiumSyncMigrationOpHandler,
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001453 self.ChromiumSyncTimeHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001454 self.ChromiumSyncDisableNotificationsOpHandler,
1455 self.ChromiumSyncEnableNotificationsOpHandler,
1456 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001457 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001458 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001459 self.ChromiumSyncSyncTabsOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001460 self.ChromiumSyncErrorOpHandler,
1461 self.ChromiumSyncCredHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001462
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001463 post_handlers = [self.ChromiumSyncCommandHandler,
1464 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001465 BasePageHandler.__init__(self, request, client_address,
1466 sync_http_server, [], get_handlers,
1467 post_handlers, [])
1468
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001469
akalin@chromium.org154bb132010-11-12 02:20:27 +00001470 def ChromiumSyncTimeHandler(self):
1471 """Handle Chromium sync .../time requests.
1472
1473 The syncer sometimes checks server reachability by examining /time.
1474 """
1475 test_name = "/chromiumsync/time"
1476 if not self._ShouldHandleRequest(test_name):
1477 return False
1478
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001479 # Chrome hates it if we send a response before reading the request.
1480 if self.headers.getheader('content-length'):
1481 length = int(self.headers.getheader('content-length'))
1482 raw_request = self.rfile.read(length)
1483
akalin@chromium.org154bb132010-11-12 02:20:27 +00001484 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001485 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001486 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001487 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001488 return True
1489
1490 def ChromiumSyncCommandHandler(self):
1491 """Handle a chromiumsync command arriving via http.
1492
1493 This covers all sync protocol commands: authentication, getupdates, and
1494 commit.
1495 """
1496 test_name = "/chromiumsync/command"
1497 if not self._ShouldHandleRequest(test_name):
1498 return False
1499
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001500 length = int(self.headers.getheader('content-length'))
1501 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001502 http_response = 200
1503 raw_reply = None
1504 if not self.server.GetAuthenticated():
1505 http_response = 401
1506 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"'
1507 else:
1508 http_response, raw_reply = self.server.HandleCommand(
1509 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001510
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001511 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001512 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001513 if http_response == 401:
1514 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001515 self.end_headers()
1516 self.wfile.write(raw_reply)
1517 return True
1518
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001519 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001520 test_name = "/chromiumsync/migrate"
1521 if not self._ShouldHandleRequest(test_name):
1522 return False
1523
1524 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1525 self.path)
1526 self.send_response(http_response)
1527 self.send_header('Content-Type', 'text/html')
1528 self.send_header('Content-Length', len(raw_reply))
1529 self.end_headers()
1530 self.wfile.write(raw_reply)
1531 return True
1532
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001533 def ChromiumSyncCredHandler(self):
1534 test_name = "/chromiumsync/cred"
1535 if not self._ShouldHandleRequest(test_name):
1536 return False
1537 try:
1538 query = urlparse.urlparse(self.path)[4]
1539 cred_valid = urlparse.parse_qs(query)['valid']
1540 if cred_valid[0] == 'True':
1541 self.server.SetAuthenticated(True)
1542 else:
1543 self.server.SetAuthenticated(False)
1544 except:
1545 self.server.SetAuthenticated(False)
1546
1547 http_response = 200
1548 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1549 self.send_response(http_response)
1550 self.send_header('Content-Type', 'text/html')
1551 self.send_header('Content-Length', len(raw_reply))
1552 self.end_headers()
1553 self.wfile.write(raw_reply)
1554 return True
1555
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001556 def ChromiumSyncDisableNotificationsOpHandler(self):
1557 test_name = "/chromiumsync/disablenotifications"
1558 if not self._ShouldHandleRequest(test_name):
1559 return False
1560 self.server.GetXmppServer().DisableNotifications()
1561 result = 200
1562 raw_reply = ('<html><title>Notifications disabled</title>'
1563 '<H1>Notifications disabled</H1></html>')
1564 self.send_response(result)
1565 self.send_header('Content-Type', 'text/html')
1566 self.send_header('Content-Length', len(raw_reply))
1567 self.end_headers()
1568 self.wfile.write(raw_reply)
1569 return True;
1570
1571 def ChromiumSyncEnableNotificationsOpHandler(self):
1572 test_name = "/chromiumsync/enablenotifications"
1573 if not self._ShouldHandleRequest(test_name):
1574 return False
1575 self.server.GetXmppServer().EnableNotifications()
1576 result = 200
1577 raw_reply = ('<html><title>Notifications enabled</title>'
1578 '<H1>Notifications enabled</H1></html>')
1579 self.send_response(result)
1580 self.send_header('Content-Type', 'text/html')
1581 self.send_header('Content-Length', len(raw_reply))
1582 self.end_headers()
1583 self.wfile.write(raw_reply)
1584 return True;
1585
1586 def ChromiumSyncSendNotificationOpHandler(self):
1587 test_name = "/chromiumsync/sendnotification"
1588 if not self._ShouldHandleRequest(test_name):
1589 return False
1590 query = urlparse.urlparse(self.path)[4]
1591 query_params = urlparse.parse_qs(query)
1592 channel = ''
1593 data = ''
1594 if 'channel' in query_params:
1595 channel = query_params['channel'][0]
1596 if 'data' in query_params:
1597 data = query_params['data'][0]
1598 self.server.GetXmppServer().SendNotification(channel, data)
1599 result = 200
1600 raw_reply = ('<html><title>Notification sent</title>'
1601 '<H1>Notification sent with channel "%s" '
1602 'and data "%s"</H1></html>'
1603 % (channel, data))
1604 self.send_response(result)
1605 self.send_header('Content-Type', 'text/html')
1606 self.send_header('Content-Length', len(raw_reply))
1607 self.end_headers()
1608 self.wfile.write(raw_reply)
1609 return True;
1610
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001611 def ChromiumSyncBirthdayErrorOpHandler(self):
1612 test_name = "/chromiumsync/birthdayerror"
1613 if not self._ShouldHandleRequest(test_name):
1614 return False
1615 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1616 self.send_response(result)
1617 self.send_header('Content-Type', 'text/html')
1618 self.send_header('Content-Length', len(raw_reply))
1619 self.end_headers()
1620 self.wfile.write(raw_reply)
1621 return True;
1622
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001623 def ChromiumSyncTransientErrorOpHandler(self):
1624 test_name = "/chromiumsync/transienterror"
1625 if not self._ShouldHandleRequest(test_name):
1626 return False
1627 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1628 self.send_response(result)
1629 self.send_header('Content-Type', 'text/html')
1630 self.send_header('Content-Length', len(raw_reply))
1631 self.end_headers()
1632 self.wfile.write(raw_reply)
1633 return True;
1634
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001635 def ChromiumSyncErrorOpHandler(self):
1636 test_name = "/chromiumsync/error"
1637 if not self._ShouldHandleRequest(test_name):
1638 return False
1639 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1640 self.path)
1641 self.send_response(result)
1642 self.send_header('Content-Type', 'text/html')
1643 self.send_header('Content-Length', len(raw_reply))
1644 self.end_headers()
1645 self.wfile.write(raw_reply)
1646 return True;
1647
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001648 def ChromiumSyncSyncTabsOpHandler(self):
1649 test_name = "/chromiumsync/synctabs"
1650 if not self._ShouldHandleRequest(test_name):
1651 return False
1652 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1653 self.send_response(result)
1654 self.send_header('Content-Type', 'text/html')
1655 self.send_header('Content-Length', len(raw_reply))
1656 self.end_headers()
1657 self.wfile.write(raw_reply)
1658 return True;
1659
akalin@chromium.org154bb132010-11-12 02:20:27 +00001660
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001661def MakeDataDir():
1662 if options.data_dir:
1663 if not os.path.isdir(options.data_dir):
1664 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1665 return None
1666 my_data_dir = options.data_dir
1667 else:
1668 # Create the default path to our data dir, relative to the exe dir.
1669 my_data_dir = os.path.dirname(sys.argv[0])
1670 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001671 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001672
1673 #TODO(ibrar): Must use Find* funtion defined in google\tools
1674 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1675
1676 return my_data_dir
1677
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001678
1679class TCPEchoHandler(SocketServer.BaseRequestHandler):
1680 """The RequestHandler class for TCP echo server.
1681
1682 It is instantiated once per connection to the server, and overrides the
1683 handle() method to implement communication to the client.
1684 """
1685
1686 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001687 """Handles the request from the client and constructs a response."""
1688
1689 data = self.request.recv(65536).strip()
1690 # Verify the "echo request" message received from the client. Send back
1691 # "echo response" message if "echo request" message is valid.
1692 try:
1693 return_data = echo_message.GetEchoResponseData(data)
1694 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001695 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001696 except ValueError:
1697 return
1698
1699 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001700
1701
1702class UDPEchoHandler(SocketServer.BaseRequestHandler):
1703 """The RequestHandler class for UDP echo server.
1704
1705 It is instantiated once per connection to the server, and overrides the
1706 handle() method to implement communication to the client.
1707 """
1708
1709 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001710 """Handles the request from the client and constructs a response."""
1711
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001712 data = self.request[0].strip()
1713 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001714 # Verify the "echo request" message received from the client. Send back
1715 # "echo response" message if "echo request" message is valid.
1716 try:
1717 return_data = echo_message.GetEchoResponseData(data)
1718 if not return_data:
1719 return
1720 except ValueError:
1721 return
1722 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001723
1724
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001725class FileMultiplexer:
1726 def __init__(self, fd1, fd2) :
1727 self.__fd1 = fd1
1728 self.__fd2 = fd2
1729
1730 def __del__(self) :
1731 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1732 self.__fd1.close()
1733 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1734 self.__fd2.close()
1735
1736 def write(self, text) :
1737 self.__fd1.write(text)
1738 self.__fd2.write(text)
1739
1740 def flush(self) :
1741 self.__fd1.flush()
1742 self.__fd2.flush()
1743
initial.commit94958cf2008-07-26 22:42:52 +00001744def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001745 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001746 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001747 if options.log_to_console:
1748 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1749 else:
1750 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001751
1752 port = options.port
1753
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001754 server_data = {}
1755
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001756 if options.server_type == SERVER_HTTP:
1757 if options.cert:
1758 # let's make sure the cert file exists.
1759 if not os.path.isfile(options.cert):
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001760 print 'specified server cert file not found: ' + options.cert + \
1761 ' exiting...'
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001762 return
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001763 for ca_cert in options.ssl_client_ca:
1764 if not os.path.isfile(ca_cert):
1765 print 'specified trusted client CA file not found: ' + ca_cert + \
1766 ' exiting...'
1767 return
davidben@chromium.org31282a12010-08-07 01:10:02 +00001768 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001769 options.ssl_client_auth, options.ssl_client_ca,
1770 options.ssl_bulk_cipher)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001771 print 'HTTPS server started on port %d...' % server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001772 else:
phajdan.jr@chromium.orgfa49b5f2010-07-23 20:38:56 +00001773 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001774 print 'HTTP server started on port %d...' % server.server_port
erikkay@google.com70397b62008-12-30 21:49:21 +00001775
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001776 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001777 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001778 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001779 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001780 server.policy_keys = options.policy_keys
1781 server.policy_user = options.policy_user
akalin@chromium.org154bb132010-11-12 02:20:27 +00001782 elif options.server_type == SERVER_SYNC:
1783 server = SyncHTTPServer(('127.0.0.1', port), SyncPageHandler)
1784 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001785 print 'Sync XMPP server started on port %d...' % server.xmpp_port
1786 server_data['port'] = server.server_port
1787 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001788 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001789 # Used for generating the key (randomly) that encodes the "echo request"
1790 # message.
1791 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001792 server = TCPEchoServer(('127.0.0.1', port), TCPEchoHandler)
1793 print 'Echo TCP server started on port %d...' % server.server_port
1794 server_data['port'] = server.server_port
1795 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001796 # Used for generating the key (randomly) that encodes the "echo request"
1797 # message.
1798 random.seed()
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001799 server = UDPEchoServer(('127.0.0.1', port), UDPEchoHandler)
1800 print 'Echo UDP server started on port %d...' % server.server_port
1801 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001802 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001803 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001804 my_data_dir = MakeDataDir()
1805
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001806 # Instantiate a dummy authorizer for managing 'virtual' users
1807 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1808
1809 # Define a new user having full r/w permissions and a read-only
1810 # anonymous user
1811 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1812
1813 authorizer.add_anonymous(my_data_dir)
1814
1815 # Instantiate FTP handler class
1816 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1817 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001818
1819 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001820 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1821 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001822
1823 # Instantiate FTP server class and listen to 127.0.0.1:port
1824 address = ('127.0.0.1', port)
1825 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001826 server_data['port'] = server.socket.getsockname()[1]
1827 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00001828
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001829 # Notify the parent that we've started. (BaseServer subclasses
1830 # bind their sockets on construction.)
1831 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001832 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001833 server_data_len = len(server_data_json)
1834 print 'sending server_data: %s (%d bytes)' % (
1835 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001836 if sys.platform == 'win32':
1837 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
1838 else:
1839 fd = options.startup_pipe
1840 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00001841 # First write the data length as an unsigned 4-byte value. This
1842 # is _not_ using network byte ordering since the other end of the
1843 # pipe is on the same machine.
1844 startup_pipe.write(struct.pack('=L', server_data_len))
1845 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001846 startup_pipe.close()
1847
initial.commit94958cf2008-07-26 22:42:52 +00001848 try:
1849 server.serve_forever()
1850 except KeyboardInterrupt:
1851 print 'shutting down server'
1852 server.stop = True
1853
1854if __name__ == '__main__':
1855 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001856 option_parser.add_option("-f", '--ftp', action='store_const',
1857 const=SERVER_FTP, default=SERVER_HTTP,
1858 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00001859 help='start up an FTP server.')
1860 option_parser.add_option('', '--sync', action='store_const',
1861 const=SERVER_SYNC, default=SERVER_HTTP,
1862 dest='server_type',
1863 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001864 option_parser.add_option('', '--tcp-echo', action='store_const',
1865 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
1866 dest='server_type',
1867 help='start up a tcp echo server.')
1868 option_parser.add_option('', '--udp-echo', action='store_const',
1869 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
1870 dest='server_type',
1871 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001872 option_parser.add_option('', '--log-to-console', action='store_const',
1873 const=True, default=False,
1874 dest='log_to_console',
1875 help='Enables or disables sys.stdout logging to '
1876 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00001877 option_parser.add_option('', '--port', default='0', type='int',
1878 help='Port used by the server. If unspecified, the '
1879 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001880 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001881 help='Directory from which to read the files.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001882 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001883 help='Specify that https should be used, specify '
1884 'the path to the cert containing the private key '
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001885 'the server should use.')
davidben@chromium.org31282a12010-08-07 01:10:02 +00001886 option_parser.add_option('', '--ssl-client-auth', action='store_true',
1887 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00001888 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
1889 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00001890 'should include the CA named in the subject of '
1891 'the DER-encoded certificate contained in the '
1892 'specified file. This option may appear multiple '
1893 'times, indicating multiple CA names should be '
1894 'sent in the request.')
1895 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
1896 help='Specify the bulk encryption algorithm(s)'
1897 'that will be accepted by the SSL server. Valid '
1898 'values are "aes256", "aes128", "3des", "rc4". If '
1899 'omitted, all algorithms will be used. This '
1900 'option may appear multiple times, indicating '
1901 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001902 option_parser.add_option('', '--file-root-url', default='/files/',
1903 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00001904 option_parser.add_option('', '--startup-pipe', type='int',
1905 dest='startup_pipe',
1906 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001907 option_parser.add_option('', '--policy-key', action='append',
1908 dest='policy_keys',
1909 help='Specify a path to a PEM-encoded private key '
1910 'to use for policy signing. May be specified '
1911 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00001912 'the server. If ther server has multiple keys, it '
1913 'will rotate through them in at each request a '
1914 'round-robin fashion. The server will generate a '
1915 'random key if none is specified on the command '
1916 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001917 option_parser.add_option('', '--policy-user', default='user@example.com',
1918 dest='policy_user',
1919 help='Specify the user name the server should '
1920 'report back to the client as the user owning the '
1921 'token used for making the policy request.')
initial.commit94958cf2008-07-26 22:42:52 +00001922 options, args = option_parser.parse_args()
1923
1924 sys.exit(main(options, args))