blob: 64619975787680a5c9a63db197cbfce70c653c8b [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00002# Copyright (c) 2012 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
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000021import httplib
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import optparse
24import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000030import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
39# Ignore deprecation warnings, they make our output more cluttered.
40warnings.filterwarnings("ignore", category=DeprecationWarning)
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000041
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000042import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000044import tlslite
45import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000046
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000047try:
48 import hashlib
49 _new_md5 = hashlib.md5
50except ImportError:
51 import md5
52 _new_md5 = md5.new
53
dpranke@chromium.org70049b72011-10-14 00:38:18 +000054try:
55 import json
56except ImportError:
57 import simplejson as json
58
davidben@chromium.org06fcf202010-09-22 18:15:23 +000059if sys.platform == 'win32':
60 import msvcrt
61
maruel@chromium.org756cf982009-03-05 12:46:38 +000062SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000063SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000064SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000065SERVER_TCP_ECHO = 3
66SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000067
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000068# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000069debug_output = sys.stderr
70def debug(str):
71 debug_output.write(str + "\n")
72 debug_output.flush()
73
agl@chromium.orgf9e66792011-12-12 22:22:19 +000074class RecordingSSLSessionCache(object):
75 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
76 lookups and inserts in order to test session cache behaviours."""
77
78 def __init__(self):
79 self.log = []
80
81 def __getitem__(self, sessionID):
82 self.log.append(('lookup', sessionID))
83 raise KeyError()
84
85 def __setitem__(self, sessionID, session):
86 self.log.append(('insert', sessionID))
87
erikwright@chromium.org847ef282012-02-22 16:41:10 +000088
89class ClientRestrictingServerMixIn:
90 """Implements verify_request to limit connections to our configured IP
91 address."""
92
93 def verify_request(self, request, client_address):
94 return client_address[0] == self.server_address[0]
95
96
initial.commit94958cf2008-07-26 22:42:52 +000097class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +000098 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +000099 to be exited cleanly (by setting its "stop" member to True)."""
100
101 def serve_forever(self):
102 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000103 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000104 while not self.stop:
105 self.handle_request()
106 self.socket.close()
107
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000108
109class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000110 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000111 verification."""
112
113 pass
114
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000115class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
116 """This is a specialization of HTTPServer that serves an
117 OCSP response"""
118
119 def serve_forever_on_thread(self):
120 self.thread = threading.Thread(target = self.serve_forever,
121 name = "OCSPServerThread")
122 self.thread.start()
123
124 def stop_serving(self):
125 self.shutdown()
126 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127
128class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
129 ClientRestrictingServerMixIn,
130 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000131 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000132 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000133
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000134 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000135 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
136 record_resume_info):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000137 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
138 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000139 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000140 self.ssl_client_cas = []
141 for ca_file in ssl_client_cas:
142 s = open(ca_file).read()
143 x509 = tlslite.api.X509()
144 x509.parse(s)
145 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000146 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
147 if ssl_bulk_ciphers is not None:
148 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000149
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000150 if record_resume_info:
151 # If record_resume_info is true then we'll replace the session cache with
152 # an object that records the lookups and inserts that it sees.
153 self.session_cache = RecordingSSLSessionCache()
154 else:
155 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000156 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
157
158 def handshake(self, tlsConnection):
159 """Creates the SSL connection."""
160 try:
161 tlsConnection.handshakeServer(certChain=self.cert_chain,
162 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000163 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000164 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000165 settings=self.ssl_handshake_settings,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000166 reqCAs=self.ssl_client_cas)
initial.commit94958cf2008-07-26 22:42:52 +0000167 tlsConnection.ignoreAbruptClose = True
168 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000169 except tlslite.api.TLSAbruptCloseError:
170 # Ignore abrupt close.
171 return True
initial.commit94958cf2008-07-26 22:42:52 +0000172 except tlslite.api.TLSError, error:
173 print "Handshake failure:", str(error)
174 return False
175
akalin@chromium.org154bb132010-11-12 02:20:27 +0000176
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000177class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000178 """An HTTP server that handles sync commands."""
179
180 def __init__(self, server_address, request_handler_class):
181 # We import here to avoid pulling in chromiumsync's dependencies
182 # unless strictly necessary.
183 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000184 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000185 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000186 self._sync_handler = chromiumsync.TestServer()
187 self._xmpp_socket_map = {}
188 self._xmpp_server = xmppserver.XmppServer(
189 self._xmpp_socket_map, ('localhost', 0))
190 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000191 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000192
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000193 def GetXmppServer(self):
194 return self._xmpp_server
195
akalin@chromium.org154bb132010-11-12 02:20:27 +0000196 def HandleCommand(self, query, raw_request):
197 return self._sync_handler.HandleCommand(query, raw_request)
198
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000199 def HandleRequestNoBlock(self):
200 """Handles a single request.
201
202 Copied from SocketServer._handle_request_noblock().
203 """
204 try:
205 request, client_address = self.get_request()
206 except socket.error:
207 return
208 if self.verify_request(request, client_address):
209 try:
210 self.process_request(request, client_address)
211 except:
212 self.handle_error(request, client_address)
213 self.close_request(request)
214
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000215 def SetAuthenticated(self, auth_valid):
216 self.authenticated = auth_valid
217
218 def GetAuthenticated(self):
219 return self.authenticated
220
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000221 def serve_forever(self):
222 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
223 """
224
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000225 def HandleXmppSocket(fd, socket_map, handler):
226 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000227
228 Adapted from asyncore.read() et al.
229 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000230 xmpp_connection = socket_map.get(fd)
231 # This could happen if a previous handler call caused fd to get
232 # removed from socket_map.
233 if xmpp_connection is None:
234 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000235 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000236 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000237 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
238 raise
239 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000240 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000241
242 while True:
243 read_fds = [ self.fileno() ]
244 write_fds = []
245 exceptional_fds = []
246
247 for fd, xmpp_connection in self._xmpp_socket_map.items():
248 is_r = xmpp_connection.readable()
249 is_w = xmpp_connection.writable()
250 if is_r:
251 read_fds.append(fd)
252 if is_w:
253 write_fds.append(fd)
254 if is_r or is_w:
255 exceptional_fds.append(fd)
256
257 try:
258 read_fds, write_fds, exceptional_fds = (
259 select.select(read_fds, write_fds, exceptional_fds))
260 except select.error, err:
261 if err.args[0] != errno.EINTR:
262 raise
263 else:
264 continue
265
266 for fd in read_fds:
267 if fd == self.fileno():
268 self.HandleRequestNoBlock()
269 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000270 HandleXmppSocket(fd, self._xmpp_socket_map,
271 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000272
273 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000274 HandleXmppSocket(fd, self._xmpp_socket_map,
275 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000276
277 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000278 HandleXmppSocket(fd, self._xmpp_socket_map,
279 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000280
akalin@chromium.org154bb132010-11-12 02:20:27 +0000281
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000282class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
283 """This is a specialization of FTPServer that adds client verification."""
284
285 pass
286
287
288class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000289 """A TCP echo server that echoes back what it has received."""
290
291 def server_bind(self):
292 """Override server_bind to store the server name."""
293 SocketServer.TCPServer.server_bind(self)
294 host, port = self.socket.getsockname()[:2]
295 self.server_name = socket.getfqdn(host)
296 self.server_port = port
297
298 def serve_forever(self):
299 self.stop = False
300 self.nonce_time = None
301 while not self.stop:
302 self.handle_request()
303 self.socket.close()
304
305
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000306class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000307 """A UDP echo server that echoes back what it has received."""
308
309 def server_bind(self):
310 """Override server_bind to store the server name."""
311 SocketServer.UDPServer.server_bind(self)
312 host, port = self.socket.getsockname()[:2]
313 self.server_name = socket.getfqdn(host)
314 self.server_port = port
315
316 def serve_forever(self):
317 self.stop = False
318 self.nonce_time = None
319 while not self.stop:
320 self.handle_request()
321 self.socket.close()
322
323
akalin@chromium.org154bb132010-11-12 02:20:27 +0000324class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
325
326 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000327 connect_handlers, get_handlers, head_handlers, post_handlers,
328 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000329 self._connect_handlers = connect_handlers
330 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000331 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000332 self._post_handlers = post_handlers
333 self._put_handlers = put_handlers
334 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
335 self, request, client_address, socket_server)
336
337 def log_request(self, *args, **kwargs):
338 # Disable request logging to declutter test log output.
339 pass
340
341 def _ShouldHandleRequest(self, handler_name):
342 """Determines if the path can be handled by the handler.
343
344 We consider a handler valid if the path begins with the
345 handler name. It can optionally be followed by "?*", "/*".
346 """
347
348 pattern = re.compile('%s($|\?|/).*' % handler_name)
349 return pattern.match(self.path)
350
351 def do_CONNECT(self):
352 for handler in self._connect_handlers:
353 if handler():
354 return
355
356 def do_GET(self):
357 for handler in self._get_handlers:
358 if handler():
359 return
360
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000361 def do_HEAD(self):
362 for handler in self._head_handlers:
363 if handler():
364 return
365
akalin@chromium.org154bb132010-11-12 02:20:27 +0000366 def do_POST(self):
367 for handler in self._post_handlers:
368 if handler():
369 return
370
371 def do_PUT(self):
372 for handler in self._put_handlers:
373 if handler():
374 return
375
376
377class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000378
379 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000380 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000381 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000382 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000383 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000384 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000385 self.NoCacheMaxAgeTimeHandler,
386 self.NoCacheTimeHandler,
387 self.CacheTimeHandler,
388 self.CacheExpiresHandler,
389 self.CacheProxyRevalidateHandler,
390 self.CachePrivateHandler,
391 self.CachePublicHandler,
392 self.CacheSMaxAgeHandler,
393 self.CacheMustRevalidateHandler,
394 self.CacheMustRevalidateMaxAgeHandler,
395 self.CacheNoStoreHandler,
396 self.CacheNoStoreMaxAgeHandler,
397 self.CacheNoTransformHandler,
398 self.DownloadHandler,
399 self.DownloadFinishHandler,
400 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000401 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000402 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000403 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000404 self.GDataAuthHandler,
405 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000406 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000407 self.SetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000408 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.AuthBasicHandler,
410 self.AuthDigestHandler,
411 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000412 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000414 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000415 self.ServerRedirectHandler,
416 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000417 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000418 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000419 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000420 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000421 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000422 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000423 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000424 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000425 self.DeviceManagementHandler,
426 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000427 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000428 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000429 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000430 head_handlers = [
431 self.FileHandler,
432 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000433
maruel@google.come250a9b2009-03-10 17:39:46 +0000434 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000435 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000436 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000437 'gif': 'image/gif',
438 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000439 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000440 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000441 'pdf' : 'application/pdf',
442 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 }
initial.commit94958cf2008-07-26 22:42:52 +0000444 self._default_mime_type = 'text/html'
445
akalin@chromium.org154bb132010-11-12 02:20:27 +0000446 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000447 connect_handlers, get_handlers, head_handlers,
448 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000449
initial.commit94958cf2008-07-26 22:42:52 +0000450 def GetMIMETypeFromName(self, file_name):
451 """Returns the mime type for the specified file_name. So far it only looks
452 at the file extension."""
453
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000454 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000455 if len(extension) == 0:
456 # no extension.
457 return self._default_mime_type
458
ericroman@google.comc17ca532009-05-07 03:51:05 +0000459 # extension starts with a dot, so we need to remove it
460 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000461
initial.commit94958cf2008-07-26 22:42:52 +0000462 def NoCacheMaxAgeTimeHandler(self):
463 """This request handler yields a page with the title set to the current
464 system time, and no caching requested."""
465
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000466 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000467 return False
468
469 self.send_response(200)
470 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000471 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000472 self.end_headers()
473
maruel@google.come250a9b2009-03-10 17:39:46 +0000474 self.wfile.write('<html><head><title>%s</title></head></html>' %
475 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000476
477 return True
478
479 def NoCacheTimeHandler(self):
480 """This request handler yields a page with the title set to the current
481 system time, and no caching requested."""
482
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000483 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000484 return False
485
486 self.send_response(200)
487 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000488 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000489 self.end_headers()
490
maruel@google.come250a9b2009-03-10 17:39:46 +0000491 self.wfile.write('<html><head><title>%s</title></head></html>' %
492 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000493
494 return True
495
496 def CacheTimeHandler(self):
497 """This request handler yields a page with the title set to the current
498 system time, and allows caching for one minute."""
499
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000500 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000501 return False
502
503 self.send_response(200)
504 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000505 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000506 self.end_headers()
507
maruel@google.come250a9b2009-03-10 17:39:46 +0000508 self.wfile.write('<html><head><title>%s</title></head></html>' %
509 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000510
511 return True
512
513 def CacheExpiresHandler(self):
514 """This request handler yields a page with the title set to the current
515 system time, and set the page to expire on 1 Jan 2099."""
516
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000517 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000518 return False
519
520 self.send_response(200)
521 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000522 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000523 self.end_headers()
524
maruel@google.come250a9b2009-03-10 17:39:46 +0000525 self.wfile.write('<html><head><title>%s</title></head></html>' %
526 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000527
528 return True
529
530 def CacheProxyRevalidateHandler(self):
531 """This request handler yields a page with the title set to the current
532 system time, and allows caching for 60 seconds"""
533
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000534 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000535 return False
536
537 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000538 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000539 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
540 self.end_headers()
541
maruel@google.come250a9b2009-03-10 17:39:46 +0000542 self.wfile.write('<html><head><title>%s</title></head></html>' %
543 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000544
545 return True
546
547 def CachePrivateHandler(self):
548 """This request handler yields a page with the title set to the current
549 system time, and allows caching for 5 seconds."""
550
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000551 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000552 return False
553
554 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000555 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000556 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000557 self.end_headers()
558
maruel@google.come250a9b2009-03-10 17:39:46 +0000559 self.wfile.write('<html><head><title>%s</title></head></html>' %
560 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000561
562 return True
563
564 def CachePublicHandler(self):
565 """This request handler yields a page with the title set to the current
566 system time, and allows caching for 5 seconds."""
567
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000568 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000569 return False
570
571 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000572 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000573 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000574 self.end_headers()
575
maruel@google.come250a9b2009-03-10 17:39:46 +0000576 self.wfile.write('<html><head><title>%s</title></head></html>' %
577 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000578
579 return True
580
581 def CacheSMaxAgeHandler(self):
582 """This request handler yields a page with the title set to the current
583 system time, and does not allow for caching."""
584
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000585 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000586 return False
587
588 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000589 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000590 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
591 self.end_headers()
592
maruel@google.come250a9b2009-03-10 17:39:46 +0000593 self.wfile.write('<html><head><title>%s</title></head></html>' %
594 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000595
596 return True
597
598 def CacheMustRevalidateHandler(self):
599 """This request handler yields a page with the title set to the current
600 system time, and does not allow caching."""
601
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000602 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000603 return False
604
605 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000606 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000607 self.send_header('Cache-Control', 'must-revalidate')
608 self.end_headers()
609
maruel@google.come250a9b2009-03-10 17:39:46 +0000610 self.wfile.write('<html><head><title>%s</title></head></html>' %
611 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000612
613 return True
614
615 def CacheMustRevalidateMaxAgeHandler(self):
616 """This request handler yields a page with the title set to the current
617 system time, and does not allow caching event though max-age of 60
618 seconds is specified."""
619
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000620 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000621 return False
622
623 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000624 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000625 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
626 self.end_headers()
627
maruel@google.come250a9b2009-03-10 17:39:46 +0000628 self.wfile.write('<html><head><title>%s</title></head></html>' %
629 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000630
631 return True
632
initial.commit94958cf2008-07-26 22:42:52 +0000633 def CacheNoStoreHandler(self):
634 """This request handler yields a page with the title set to the current
635 system time, and does not allow the page to be stored."""
636
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000637 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000638 return False
639
640 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000641 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000642 self.send_header('Cache-Control', 'no-store')
643 self.end_headers()
644
maruel@google.come250a9b2009-03-10 17:39:46 +0000645 self.wfile.write('<html><head><title>%s</title></head></html>' %
646 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000647
648 return True
649
650 def CacheNoStoreMaxAgeHandler(self):
651 """This request handler yields a page with the title set to the current
652 system time, and does not allow the page to be stored even though max-age
653 of 60 seconds is specified."""
654
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000655 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000656 return False
657
658 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000659 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000660 self.send_header('Cache-Control', 'max-age=60, no-store')
661 self.end_headers()
662
maruel@google.come250a9b2009-03-10 17:39:46 +0000663 self.wfile.write('<html><head><title>%s</title></head></html>' %
664 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000665
666 return True
667
668
669 def CacheNoTransformHandler(self):
670 """This request handler yields a page with the title set to the current
671 system time, and does not allow the content to transformed during
672 user-agent caching"""
673
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000674 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000675 return False
676
677 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000678 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000679 self.send_header('Cache-Control', 'no-transform')
680 self.end_headers()
681
maruel@google.come250a9b2009-03-10 17:39:46 +0000682 self.wfile.write('<html><head><title>%s</title></head></html>' %
683 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000684
685 return True
686
687 def EchoHeader(self):
688 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000689 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000690
ananta@chromium.org56812d02011-04-07 17:52:05 +0000691 """This function echoes back the value of a specific request header"""
692 """while allowing caching for 16 hours."""
693 def EchoHeaderCache(self):
694 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000695
696 def EchoHeaderHelper(self, echo_header):
697 """This function echoes back the value of the request header passed in."""
698 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000699 return False
700
701 query_char = self.path.find('?')
702 if query_char != -1:
703 header_name = self.path[query_char+1:]
704
705 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000706 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000707 if echo_header == '/echoheadercache':
708 self.send_header('Cache-control', 'max-age=60000')
709 else:
710 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000711 # insert a vary header to properly indicate that the cachability of this
712 # request is subject to value of the request header being echoed.
713 if len(header_name) > 0:
714 self.send_header('Vary', header_name)
715 self.end_headers()
716
717 if len(header_name) > 0:
718 self.wfile.write(self.headers.getheader(header_name))
719
720 return True
721
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000722 def ReadRequestBody(self):
723 """This function reads the body of the current HTTP request, handling
724 both plain and chunked transfer encoded requests."""
725
726 if self.headers.getheader('transfer-encoding') != 'chunked':
727 length = int(self.headers.getheader('content-length'))
728 return self.rfile.read(length)
729
730 # Read the request body as chunks.
731 body = ""
732 while True:
733 line = self.rfile.readline()
734 length = int(line, 16)
735 if length == 0:
736 self.rfile.readline()
737 break
738 body += self.rfile.read(length)
739 self.rfile.read(2)
740 return body
741
initial.commit94958cf2008-07-26 22:42:52 +0000742 def EchoHandler(self):
743 """This handler just echoes back the payload of the request, for testing
744 form submission."""
745
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000746 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000747 return False
748
749 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000750 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000751 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000752 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000753 return True
754
755 def EchoTitleHandler(self):
756 """This handler is like Echo, but sets the page title to the request."""
757
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000758 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000759 return False
760
761 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000762 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000763 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000764 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000765 self.wfile.write('<html><head><title>')
766 self.wfile.write(request)
767 self.wfile.write('</title></head></html>')
768 return True
769
770 def EchoAllHandler(self):
771 """This handler yields a (more) human-readable page listing information
772 about the request header & contents."""
773
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000774 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000775 return False
776
777 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000778 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000779 self.end_headers()
780 self.wfile.write('<html><head><style>'
781 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
782 '</style></head><body>'
783 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000784 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000785 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000786
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000787 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000788 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000789 params = cgi.parse_qs(qs, keep_blank_values=1)
790
791 for param in params:
792 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000793
794 self.wfile.write('</pre>')
795
796 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
797
798 self.wfile.write('</body></html>')
799 return True
800
801 def DownloadHandler(self):
802 """This handler sends a downloadable file with or without reporting
803 the size (6K)."""
804
805 if self.path.startswith("/download-unknown-size"):
806 send_length = False
807 elif self.path.startswith("/download-known-size"):
808 send_length = True
809 else:
810 return False
811
812 #
813 # The test which uses this functionality is attempting to send
814 # small chunks of data to the client. Use a fairly large buffer
815 # so that we'll fill chrome's IO buffer enough to force it to
816 # actually write the data.
817 # See also the comments in the client-side of this test in
818 # download_uitest.cc
819 #
820 size_chunk1 = 35*1024
821 size_chunk2 = 10*1024
822
823 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000824 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000825 self.send_header('Cache-Control', 'max-age=0')
826 if send_length:
827 self.send_header('Content-Length', size_chunk1 + size_chunk2)
828 self.end_headers()
829
830 # First chunk of data:
831 self.wfile.write("*" * size_chunk1)
832 self.wfile.flush()
833
834 # handle requests until one of them clears this flag.
835 self.server.waitForDownload = True
836 while self.server.waitForDownload:
837 self.server.handle_request()
838
839 # Second chunk of data:
840 self.wfile.write("*" * size_chunk2)
841 return True
842
843 def DownloadFinishHandler(self):
844 """This handler just tells the server to finish the current download."""
845
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000846 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000847 return False
848
849 self.server.waitForDownload = False
850 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000851 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000852 self.send_header('Cache-Control', 'max-age=0')
853 self.end_headers()
854 return True
855
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000856 def _ReplaceFileData(self, data, query_parameters):
857 """Replaces matching substrings in a file.
858
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000859 If the 'replace_text' URL query parameter is present, it is expected to be
860 of the form old_text:new_text, which indicates that any old_text strings in
861 the file are replaced with new_text. Multiple 'replace_text' parameters may
862 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000863
864 If the parameters are not present, |data| is returned.
865 """
866 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000867 replace_text_values = query_dict.get('replace_text', [])
868 for replace_text_value in replace_text_values:
869 replace_text_args = replace_text_value.split(':')
870 if len(replace_text_args) != 2:
871 raise ValueError(
872 'replace_text must be of form old_text:new_text. Actual value: %s' %
873 replace_text_value)
874 old_text_b64, new_text_b64 = replace_text_args
875 old_text = base64.urlsafe_b64decode(old_text_b64)
876 new_text = base64.urlsafe_b64decode(new_text_b64)
877 data = data.replace(old_text, new_text)
878 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000879
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000880 def ZipFileHandler(self):
881 """This handler sends the contents of the requested file in compressed form.
882 Can pass in a parameter that specifies that the content length be
883 C - the compressed size (OK),
884 U - the uncompressed size (Non-standard, but handled),
885 S - less than compressed (OK because we keep going),
886 M - larger than compressed but less than uncompressed (an error),
887 L - larger than uncompressed (an error)
888 Example: compressedfiles/Picture_1.doc?C
889 """
890
891 prefix = "/compressedfiles/"
892 if not self.path.startswith(prefix):
893 return False
894
895 # Consume a request body if present.
896 if self.command == 'POST' or self.command == 'PUT' :
897 self.ReadRequestBody()
898
899 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
900
901 if not query in ('C', 'U', 'S', 'M', 'L'):
902 return False
903
904 sub_path = url_path[len(prefix):]
905 entries = sub_path.split('/')
906 file_path = os.path.join(self.server.data_dir, *entries)
907 if os.path.isdir(file_path):
908 file_path = os.path.join(file_path, 'index.html')
909
910 if not os.path.isfile(file_path):
911 print "File not found " + sub_path + " full path:" + file_path
912 self.send_error(404)
913 return True
914
915 f = open(file_path, "rb")
916 data = f.read()
917 uncompressed_len = len(data)
918 f.close()
919
920 # Compress the data.
921 data = zlib.compress(data)
922 compressed_len = len(data)
923
924 content_length = compressed_len
925 if query == 'U':
926 content_length = uncompressed_len
927 elif query == 'S':
928 content_length = compressed_len / 2
929 elif query == 'M':
930 content_length = (compressed_len + uncompressed_len) / 2
931 elif query == 'L':
932 content_length = compressed_len + uncompressed_len
933
934 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000935 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000936 self.send_header('Content-encoding', 'deflate')
937 self.send_header('Connection', 'close')
938 self.send_header('Content-Length', content_length)
939 self.send_header('ETag', '\'' + file_path + '\'')
940 self.end_headers()
941
942 self.wfile.write(data)
943
944 return True
945
initial.commit94958cf2008-07-26 22:42:52 +0000946 def FileHandler(self):
947 """This handler sends the contents of the requested file. Wow, it's like
948 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000949 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000950 if not self.path.startswith(prefix):
951 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000952 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000953
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000954 def PostOnlyFileHandler(self):
955 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000956 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000957 if not self.path.startswith(prefix):
958 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000959 return self._FileHandlerHelper(prefix)
960
961 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000962 request_body = ''
963 if self.command == 'POST' or self.command == 'PUT':
964 # Consume a request body if present.
965 request_body = self.ReadRequestBody()
966
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000967 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000968 query_dict = cgi.parse_qs(query)
969
970 expected_body = query_dict.get('expected_body', [])
971 if expected_body and request_body not in expected_body:
972 self.send_response(404)
973 self.end_headers()
974 self.wfile.write('')
975 return True
976
977 expected_headers = query_dict.get('expected_headers', [])
978 for expected_header in expected_headers:
979 header_name, expected_value = expected_header.split(':')
980 if self.headers.getheader(header_name) != expected_value:
981 self.send_response(404)
982 self.end_headers()
983 self.wfile.write('')
984 return True
985
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000986 sub_path = url_path[len(prefix):]
987 entries = sub_path.split('/')
988 file_path = os.path.join(self.server.data_dir, *entries)
989 if os.path.isdir(file_path):
990 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000991
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000992 if not os.path.isfile(file_path):
993 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000994 self.send_error(404)
995 return True
996
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000997 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000998 data = f.read()
999 f.close()
1000
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001001 data = self._ReplaceFileData(data, query)
1002
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001003 old_protocol_version = self.protocol_version
1004
initial.commit94958cf2008-07-26 22:42:52 +00001005 # If file.mock-http-headers exists, it contains the headers we
1006 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001007 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001008 if os.path.isfile(headers_path):
1009 f = open(headers_path, "r")
1010
1011 # "HTTP/1.1 200 OK"
1012 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001013 http_major, http_minor, status_code = re.findall(
1014 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1015 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001016 self.send_response(int(status_code))
1017
1018 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001019 header_values = re.findall('(\S+):\s*(.*)', line)
1020 if len(header_values) > 0:
1021 # "name: value"
1022 name, value = header_values[0]
1023 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001024 f.close()
1025 else:
1026 # Could be more generic once we support mime-type sniffing, but for
1027 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001028
1029 range = self.headers.get('Range')
1030 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001031 # Note this doesn't handle all valid byte range values (i.e. left
1032 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001033 range = range[6:].split('-')
1034 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001035 if range[1]:
1036 end = int(range[1])
1037 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001038 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001039
1040 self.send_response(206)
1041 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1042 str(len(data))
1043 self.send_header('Content-Range', content_range)
1044 data = data[start: end + 1]
1045 else:
1046 self.send_response(200)
1047
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001048 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001049 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001050 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001051 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001052 self.end_headers()
1053
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001054 if (self.command != 'HEAD'):
1055 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001056
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001057 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001058 return True
1059
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001060 def SetCookieHandler(self):
1061 """This handler just sets a cookie, for testing cookie handling."""
1062
1063 if not self._ShouldHandleRequest("/set-cookie"):
1064 return False
1065
1066 query_char = self.path.find('?')
1067 if query_char != -1:
1068 cookie_values = self.path[query_char + 1:].split('&')
1069 else:
1070 cookie_values = ("",)
1071 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001072 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001073 for cookie_value in cookie_values:
1074 self.send_header('Set-Cookie', '%s' % cookie_value)
1075 self.end_headers()
1076 for cookie_value in cookie_values:
1077 self.wfile.write('%s' % cookie_value)
1078 return True
1079
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001080 def SetHeaderHandler(self):
1081 """This handler sets a response header. Parameters are in the
1082 key%3A%20value&key2%3A%20value2 format."""
1083
1084 if not self._ShouldHandleRequest("/set-header"):
1085 return False
1086
1087 query_char = self.path.find('?')
1088 if query_char != -1:
1089 headers_values = self.path[query_char + 1:].split('&')
1090 else:
1091 headers_values = ("",)
1092 self.send_response(200)
1093 self.send_header('Content-Type', 'text/html')
1094 for header_value in headers_values:
1095 header_value = urllib.unquote(header_value)
1096 (key, value) = header_value.split(': ', 1)
1097 self.send_header(key, value)
1098 self.end_headers()
1099 for header_value in headers_values:
1100 self.wfile.write('%s' % header_value)
1101 return True
1102
initial.commit94958cf2008-07-26 22:42:52 +00001103 def AuthBasicHandler(self):
1104 """This handler tests 'Basic' authentication. It just sends a page with
1105 title 'user/pass' if you succeed."""
1106
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001107 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001108 return False
1109
1110 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001111 expected_password = 'secret'
1112 realm = 'testrealm'
1113 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001114
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001115 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1116 query_params = cgi.parse_qs(query, True)
1117 if 'set-cookie-if-challenged' in query_params:
1118 set_cookie_if_challenged = True
1119 if 'password' in query_params:
1120 expected_password = query_params['password'][0]
1121 if 'realm' in query_params:
1122 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001123
initial.commit94958cf2008-07-26 22:42:52 +00001124 auth = self.headers.getheader('authorization')
1125 try:
1126 if not auth:
1127 raise Exception('no auth')
1128 b64str = re.findall(r'Basic (\S+)', auth)[0]
1129 userpass = base64.b64decode(b64str)
1130 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001131 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001132 raise Exception('wrong password')
1133 except Exception, e:
1134 # Authentication failed.
1135 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001136 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001137 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001138 if set_cookie_if_challenged:
1139 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001140 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('b64str=%s<p>' % b64str)
1146 self.wfile.write('username: %s<p>' % username)
1147 self.wfile.write('userpass: %s<p>' % userpass)
1148 self.wfile.write('password: %s<p>' % password)
1149 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1150 self.wfile.write('</body></html>')
1151 return True
1152
1153 # Authentication successful. (Return a cachable response to allow for
1154 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001155 old_protocol_version = self.protocol_version
1156 self.protocol_version = "HTTP/1.1"
1157
initial.commit94958cf2008-07-26 22:42:52 +00001158 if_none_match = self.headers.getheader('if-none-match')
1159 if if_none_match == "abc":
1160 self.send_response(304)
1161 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001162 elif url_path.endswith(".gif"):
1163 # Using chrome/test/data/google/logo.gif as the test image
1164 test_image_path = ['google', 'logo.gif']
1165 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1166 if not os.path.isfile(gif_path):
1167 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001168 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001169 return True
1170
1171 f = open(gif_path, "rb")
1172 data = f.read()
1173 f.close()
1174
1175 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001176 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001177 self.send_header('Cache-control', 'max-age=60000')
1178 self.send_header('Etag', 'abc')
1179 self.end_headers()
1180 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001181 else:
1182 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001183 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001184 self.send_header('Cache-control', 'max-age=60000')
1185 self.send_header('Etag', 'abc')
1186 self.end_headers()
1187 self.wfile.write('<html><head>')
1188 self.wfile.write('<title>%s/%s</title>' % (username, password))
1189 self.wfile.write('</head><body>')
1190 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001191 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001192 self.wfile.write('</body></html>')
1193
rvargas@google.com54453b72011-05-19 01:11:11 +00001194 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001195 return True
1196
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001197 def GDataAuthHandler(self):
1198 """This handler verifies the Authentication header for GData requests."""
1199 if not self.server.gdata_auth_token:
1200 # --auth-token is not specified, not the test case for GData.
1201 return False
1202
1203 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1204 return False
1205
1206 if 'GData-Version' not in self.headers:
1207 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1208 return True
1209
1210 if 'Authorization' not in self.headers:
1211 self.send_error(httplib.UNAUTHORIZED)
1212 return True
1213
1214 field_prefix = 'Bearer '
1215 authorization = self.headers['Authorization']
1216 if not authorization.startswith(field_prefix):
1217 self.send_error(httplib.UNAUTHORIZED)
1218 return True
1219
1220 code = authorization[len(field_prefix):]
1221 if code != self.server.gdata_auth_token:
1222 self.send_error(httplib.UNAUTHORIZED)
1223 return True
1224
1225 return False
1226
1227 def GDataDocumentsFeedQueryHandler(self):
1228 """This handler verifies if required parameters are properly
1229 specified for the GData DocumentsFeed request."""
1230 if not self.server.gdata_auth_token:
1231 # --auth-token is not specified, not the test case for GData.
1232 return False
1233
1234 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1235 return False
1236
1237 (path, question, query_params) = self.path.partition('?')
1238 self.query_params = urlparse.parse_qs(query_params)
1239
1240 if 'v' not in self.query_params:
1241 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1242 return True
1243 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1244 # currently our GData client only uses JSON format.
1245 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1246 return True
1247
1248 return False
1249
tonyg@chromium.org75054202010-03-31 22:06:10 +00001250 def GetNonce(self, force_reset=False):
1251 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001252
tonyg@chromium.org75054202010-03-31 22:06:10 +00001253 This is a fake implementation. A real implementation would only use a given
1254 nonce a single time (hence the name n-once). However, for the purposes of
1255 unittesting, we don't care about the security of the nonce.
1256
1257 Args:
1258 force_reset: Iff set, the nonce will be changed. Useful for testing the
1259 "stale" response.
1260 """
1261 if force_reset or not self.server.nonce_time:
1262 self.server.nonce_time = time.time()
1263 return _new_md5('privatekey%s%d' %
1264 (self.path, self.server.nonce_time)).hexdigest()
1265
1266 def AuthDigestHandler(self):
1267 """This handler tests 'Digest' authentication.
1268
1269 It just sends a page with title 'user/pass' if you succeed.
1270
1271 A stale response is sent iff "stale" is present in the request path.
1272 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001273 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001274 return False
1275
tonyg@chromium.org75054202010-03-31 22:06:10 +00001276 stale = 'stale' in self.path
1277 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001278 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001279 password = 'secret'
1280 realm = 'testrealm'
1281
1282 auth = self.headers.getheader('authorization')
1283 pairs = {}
1284 try:
1285 if not auth:
1286 raise Exception('no auth')
1287 if not auth.startswith('Digest'):
1288 raise Exception('not digest')
1289 # Pull out all the name="value" pairs as a dictionary.
1290 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1291
1292 # Make sure it's all valid.
1293 if pairs['nonce'] != nonce:
1294 raise Exception('wrong nonce')
1295 if pairs['opaque'] != opaque:
1296 raise Exception('wrong opaque')
1297
1298 # Check the 'response' value and make sure it matches our magic hash.
1299 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001300 hash_a1 = _new_md5(
1301 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001302 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001303 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001304 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001305 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1306 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001307 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001308
1309 if pairs['response'] != response:
1310 raise Exception('wrong password')
1311 except Exception, e:
1312 # Authentication failed.
1313 self.send_response(401)
1314 hdr = ('Digest '
1315 'realm="%s", '
1316 'domain="/", '
1317 'qop="auth", '
1318 'algorithm=MD5, '
1319 'nonce="%s", '
1320 'opaque="%s"') % (realm, nonce, opaque)
1321 if stale:
1322 hdr += ', stale="TRUE"'
1323 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001324 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001325 self.end_headers()
1326 self.wfile.write('<html><head>')
1327 self.wfile.write('<title>Denied: %s</title>' % e)
1328 self.wfile.write('</head><body>')
1329 self.wfile.write('auth=%s<p>' % auth)
1330 self.wfile.write('pairs=%s<p>' % pairs)
1331 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1332 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1333 self.wfile.write('</body></html>')
1334 return True
1335
1336 # Authentication successful.
1337 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001338 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001339 self.end_headers()
1340 self.wfile.write('<html><head>')
1341 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1342 self.wfile.write('</head><body>')
1343 self.wfile.write('auth=%s<p>' % auth)
1344 self.wfile.write('pairs=%s<p>' % pairs)
1345 self.wfile.write('</body></html>')
1346
1347 return True
1348
1349 def SlowServerHandler(self):
1350 """Wait for the user suggested time before responding. The syntax is
1351 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001352 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001353 return False
1354 query_char = self.path.find('?')
1355 wait_sec = 1.0
1356 if query_char >= 0:
1357 try:
1358 wait_sec = int(self.path[query_char + 1:])
1359 except ValueError:
1360 pass
1361 time.sleep(wait_sec)
1362 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001363 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001364 self.end_headers()
1365 self.wfile.write("waited %d seconds" % wait_sec)
1366 return True
1367
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001368 def ChunkedServerHandler(self):
1369 """Send chunked response. Allows to specify chunks parameters:
1370 - waitBeforeHeaders - ms to wait before sending headers
1371 - waitBetweenChunks - ms to wait between chunks
1372 - chunkSize - size of each chunk in bytes
1373 - chunksNumber - number of chunks
1374 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1375 waits one second, then sends headers and five chunks five bytes each."""
1376 if not self._ShouldHandleRequest("/chunked"):
1377 return False
1378 query_char = self.path.find('?')
1379 chunkedSettings = {'waitBeforeHeaders' : 0,
1380 'waitBetweenChunks' : 0,
1381 'chunkSize' : 5,
1382 'chunksNumber' : 5}
1383 if query_char >= 0:
1384 params = self.path[query_char + 1:].split('&')
1385 for param in params:
1386 keyValue = param.split('=')
1387 if len(keyValue) == 2:
1388 try:
1389 chunkedSettings[keyValue[0]] = int(keyValue[1])
1390 except ValueError:
1391 pass
1392 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1393 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1394 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001395 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001396 self.send_header('Connection', 'close')
1397 self.send_header('Transfer-Encoding', 'chunked')
1398 self.end_headers()
1399 # Chunked encoding: sending all chunks, then final zero-length chunk and
1400 # then final CRLF.
1401 for i in range(0, chunkedSettings['chunksNumber']):
1402 if i > 0:
1403 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1404 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1405 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1406 self.sendChunkHelp('')
1407 return True
1408
initial.commit94958cf2008-07-26 22:42:52 +00001409 def ContentTypeHandler(self):
1410 """Returns a string of html with the given content type. E.g.,
1411 /contenttype?text/css returns an html file with the Content-Type
1412 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001413 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001414 return False
1415 query_char = self.path.find('?')
1416 content_type = self.path[query_char + 1:].strip()
1417 if not content_type:
1418 content_type = 'text/html'
1419 self.send_response(200)
1420 self.send_header('Content-Type', content_type)
1421 self.end_headers()
1422 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1423 return True
1424
creis@google.com2f4f6a42011-03-25 19:44:19 +00001425 def NoContentHandler(self):
1426 """Returns a 204 No Content response."""
1427 if not self._ShouldHandleRequest("/nocontent"):
1428 return False
1429 self.send_response(204)
1430 self.end_headers()
1431 return True
1432
initial.commit94958cf2008-07-26 22:42:52 +00001433 def ServerRedirectHandler(self):
1434 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001435 '/server-redirect?http://foo.bar/asdf' to redirect to
1436 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001437
1438 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001439 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001440 return False
1441
1442 query_char = self.path.find('?')
1443 if query_char < 0 or len(self.path) <= query_char + 1:
1444 self.sendRedirectHelp(test_name)
1445 return True
1446 dest = self.path[query_char + 1:]
1447
1448 self.send_response(301) # moved permanently
1449 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001450 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001451 self.end_headers()
1452 self.wfile.write('<html><head>')
1453 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1454
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001455 return True
initial.commit94958cf2008-07-26 22:42:52 +00001456
1457 def ClientRedirectHandler(self):
1458 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001459 '/client-redirect?http://foo.bar/asdf' to redirect to
1460 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001461
1462 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001463 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001464 return False
1465
1466 query_char = self.path.find('?');
1467 if query_char < 0 or len(self.path) <= query_char + 1:
1468 self.sendRedirectHelp(test_name)
1469 return True
1470 dest = self.path[query_char + 1:]
1471
1472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001473 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001474 self.end_headers()
1475 self.wfile.write('<html><head>')
1476 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1477 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1478
1479 return True
1480
tony@chromium.org03266982010-03-05 03:18:42 +00001481 def MultipartHandler(self):
1482 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001483 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001484 if not self._ShouldHandleRequest(test_name):
1485 return False
1486
1487 num_frames = 10
1488 bound = '12345'
1489 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001490 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001491 'multipart/x-mixed-replace;boundary=' + bound)
1492 self.end_headers()
1493
1494 for i in xrange(num_frames):
1495 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001496 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001497 self.wfile.write('<title>page ' + str(i) + '</title>')
1498 self.wfile.write('page ' + str(i))
1499
1500 self.wfile.write('--' + bound + '--')
1501 return True
1502
tony@chromium.org4cb88302011-09-27 22:13:49 +00001503 def MultipartSlowHandler(self):
1504 """Send a multipart response (3 text/html pages) with a slight delay
1505 between each page. This is similar to how some pages show status using
1506 multipart."""
1507 test_name = '/multipart-slow'
1508 if not self._ShouldHandleRequest(test_name):
1509 return False
1510
1511 num_frames = 3
1512 bound = '12345'
1513 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001514 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001515 'multipart/x-mixed-replace;boundary=' + bound)
1516 self.end_headers()
1517
1518 for i in xrange(num_frames):
1519 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001520 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001521 time.sleep(0.25)
1522 if i == 2:
1523 self.wfile.write('<title>PASS</title>')
1524 else:
1525 self.wfile.write('<title>page ' + str(i) + '</title>')
1526 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1527
1528 self.wfile.write('--' + bound + '--')
1529 return True
1530
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001531 def GetSSLSessionCacheHandler(self):
1532 """Send a reply containing a log of the session cache operations."""
1533
1534 if not self._ShouldHandleRequest('/ssl-session-cache'):
1535 return False
1536
1537 self.send_response(200)
1538 self.send_header('Content-Type', 'text/plain')
1539 self.end_headers()
1540 try:
1541 for (action, sessionID) in self.server.session_cache.log:
1542 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1543 except AttributeError, e:
1544 self.wfile.write('Pass --https-record-resume in order to use' +
1545 ' this request')
1546 return True
1547
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001548 def CloseSocketHandler(self):
1549 """Closes the socket without sending anything."""
1550
1551 if not self._ShouldHandleRequest('/close-socket'):
1552 return False
1553
1554 self.wfile.close()
1555 return True
1556
initial.commit94958cf2008-07-26 22:42:52 +00001557 def DefaultResponseHandler(self):
1558 """This is the catch-all response handler for requests that aren't handled
1559 by one of the special handlers above.
1560 Note that we specify the content-length as without it the https connection
1561 is not closed properly (and the browser keeps expecting data)."""
1562
1563 contents = "Default response given for path: " + self.path
1564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001565 self.send_header('Content-Type', 'text/html')
1566 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001567 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001568 if (self.command != 'HEAD'):
1569 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001570 return True
1571
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001572 def RedirectConnectHandler(self):
1573 """Sends a redirect to the CONNECT request for www.redirect.com. This
1574 response is not specified by the RFC, so the browser should not follow
1575 the redirect."""
1576
1577 if (self.path.find("www.redirect.com") < 0):
1578 return False
1579
1580 dest = "http://www.destination.com/foo.js"
1581
1582 self.send_response(302) # moved temporarily
1583 self.send_header('Location', dest)
1584 self.send_header('Connection', 'close')
1585 self.end_headers()
1586 return True
1587
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001588 def ServerAuthConnectHandler(self):
1589 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1590 response doesn't make sense because the proxy server cannot request
1591 server authentication."""
1592
1593 if (self.path.find("www.server-auth.com") < 0):
1594 return False
1595
1596 challenge = 'Basic realm="WallyWorld"'
1597
1598 self.send_response(401) # unauthorized
1599 self.send_header('WWW-Authenticate', challenge)
1600 self.send_header('Connection', 'close')
1601 self.end_headers()
1602 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001603
1604 def DefaultConnectResponseHandler(self):
1605 """This is the catch-all response handler for CONNECT requests that aren't
1606 handled by one of the special handlers above. Real Web servers respond
1607 with 400 to CONNECT requests."""
1608
1609 contents = "Your client has issued a malformed or illegal request."
1610 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001611 self.send_header('Content-Type', 'text/html')
1612 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001613 self.end_headers()
1614 self.wfile.write(contents)
1615 return True
1616
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001617 def DeviceManagementHandler(self):
1618 """Delegates to the device management service used for cloud policy."""
1619 if not self._ShouldHandleRequest("/device_management"):
1620 return False
1621
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001622 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001623
1624 if not self.server._device_management_handler:
1625 import device_management
1626 policy_path = os.path.join(self.server.data_dir, 'device_management')
1627 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001628 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001629 self.server.policy_keys,
1630 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001631
1632 http_response, raw_reply = (
1633 self.server._device_management_handler.HandleRequest(self.path,
1634 self.headers,
1635 raw_request))
1636 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001637 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001638 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001639 self.end_headers()
1640 self.wfile.write(raw_reply)
1641 return True
1642
initial.commit94958cf2008-07-26 22:42:52 +00001643 # called by the redirect handling function when there is no parameter
1644 def sendRedirectHelp(self, redirect_name):
1645 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001646 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001647 self.end_headers()
1648 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1649 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1650 self.wfile.write('</body></html>')
1651
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001652 # called by chunked handling function
1653 def sendChunkHelp(self, chunk):
1654 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1655 self.wfile.write('%X\r\n' % len(chunk))
1656 self.wfile.write(chunk)
1657 self.wfile.write('\r\n')
1658
akalin@chromium.org154bb132010-11-12 02:20:27 +00001659
1660class SyncPageHandler(BasePageHandler):
1661 """Handler for the main HTTP sync server."""
1662
1663 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001664 get_handlers = [self.ChromiumSyncTimeHandler,
1665 self.ChromiumSyncMigrationOpHandler,
1666 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001667 self.ChromiumSyncDisableNotificationsOpHandler,
1668 self.ChromiumSyncEnableNotificationsOpHandler,
1669 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001670 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001671 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001672 self.ChromiumSyncErrorOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001673 self.ChromiumSyncSyncTabsOpHandler,
1674 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001675
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001676 post_handlers = [self.ChromiumSyncCommandHandler,
1677 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001678 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001679 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001680 post_handlers, [])
1681
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001682
akalin@chromium.org154bb132010-11-12 02:20:27 +00001683 def ChromiumSyncTimeHandler(self):
1684 """Handle Chromium sync .../time requests.
1685
1686 The syncer sometimes checks server reachability by examining /time.
1687 """
1688 test_name = "/chromiumsync/time"
1689 if not self._ShouldHandleRequest(test_name):
1690 return False
1691
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001692 # Chrome hates it if we send a response before reading the request.
1693 if self.headers.getheader('content-length'):
1694 length = int(self.headers.getheader('content-length'))
1695 raw_request = self.rfile.read(length)
1696
akalin@chromium.org154bb132010-11-12 02:20:27 +00001697 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001698 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001699 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001700 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001701 return True
1702
1703 def ChromiumSyncCommandHandler(self):
1704 """Handle a chromiumsync command arriving via http.
1705
1706 This covers all sync protocol commands: authentication, getupdates, and
1707 commit.
1708 """
1709 test_name = "/chromiumsync/command"
1710 if not self._ShouldHandleRequest(test_name):
1711 return False
1712
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001713 length = int(self.headers.getheader('content-length'))
1714 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001715 http_response = 200
1716 raw_reply = None
1717 if not self.server.GetAuthenticated():
1718 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001719 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1720 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001721 else:
1722 http_response, raw_reply = self.server.HandleCommand(
1723 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001724
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001725 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001726 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001727 if http_response == 401:
1728 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001729 self.end_headers()
1730 self.wfile.write(raw_reply)
1731 return True
1732
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001733 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001734 test_name = "/chromiumsync/migrate"
1735 if not self._ShouldHandleRequest(test_name):
1736 return False
1737
1738 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1739 self.path)
1740 self.send_response(http_response)
1741 self.send_header('Content-Type', 'text/html')
1742 self.send_header('Content-Length', len(raw_reply))
1743 self.end_headers()
1744 self.wfile.write(raw_reply)
1745 return True
1746
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001747 def ChromiumSyncCredHandler(self):
1748 test_name = "/chromiumsync/cred"
1749 if not self._ShouldHandleRequest(test_name):
1750 return False
1751 try:
1752 query = urlparse.urlparse(self.path)[4]
1753 cred_valid = urlparse.parse_qs(query)['valid']
1754 if cred_valid[0] == 'True':
1755 self.server.SetAuthenticated(True)
1756 else:
1757 self.server.SetAuthenticated(False)
1758 except:
1759 self.server.SetAuthenticated(False)
1760
1761 http_response = 200
1762 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1763 self.send_response(http_response)
1764 self.send_header('Content-Type', 'text/html')
1765 self.send_header('Content-Length', len(raw_reply))
1766 self.end_headers()
1767 self.wfile.write(raw_reply)
1768 return True
1769
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001770 def ChromiumSyncDisableNotificationsOpHandler(self):
1771 test_name = "/chromiumsync/disablenotifications"
1772 if not self._ShouldHandleRequest(test_name):
1773 return False
1774 self.server.GetXmppServer().DisableNotifications()
1775 result = 200
1776 raw_reply = ('<html><title>Notifications disabled</title>'
1777 '<H1>Notifications disabled</H1></html>')
1778 self.send_response(result)
1779 self.send_header('Content-Type', 'text/html')
1780 self.send_header('Content-Length', len(raw_reply))
1781 self.end_headers()
1782 self.wfile.write(raw_reply)
1783 return True;
1784
1785 def ChromiumSyncEnableNotificationsOpHandler(self):
1786 test_name = "/chromiumsync/enablenotifications"
1787 if not self._ShouldHandleRequest(test_name):
1788 return False
1789 self.server.GetXmppServer().EnableNotifications()
1790 result = 200
1791 raw_reply = ('<html><title>Notifications enabled</title>'
1792 '<H1>Notifications enabled</H1></html>')
1793 self.send_response(result)
1794 self.send_header('Content-Type', 'text/html')
1795 self.send_header('Content-Length', len(raw_reply))
1796 self.end_headers()
1797 self.wfile.write(raw_reply)
1798 return True;
1799
1800 def ChromiumSyncSendNotificationOpHandler(self):
1801 test_name = "/chromiumsync/sendnotification"
1802 if not self._ShouldHandleRequest(test_name):
1803 return False
1804 query = urlparse.urlparse(self.path)[4]
1805 query_params = urlparse.parse_qs(query)
1806 channel = ''
1807 data = ''
1808 if 'channel' in query_params:
1809 channel = query_params['channel'][0]
1810 if 'data' in query_params:
1811 data = query_params['data'][0]
1812 self.server.GetXmppServer().SendNotification(channel, data)
1813 result = 200
1814 raw_reply = ('<html><title>Notification sent</title>'
1815 '<H1>Notification sent with channel "%s" '
1816 'and data "%s"</H1></html>'
1817 % (channel, data))
1818 self.send_response(result)
1819 self.send_header('Content-Type', 'text/html')
1820 self.send_header('Content-Length', len(raw_reply))
1821 self.end_headers()
1822 self.wfile.write(raw_reply)
1823 return True;
1824
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001825 def ChromiumSyncBirthdayErrorOpHandler(self):
1826 test_name = "/chromiumsync/birthdayerror"
1827 if not self._ShouldHandleRequest(test_name):
1828 return False
1829 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1830 self.send_response(result)
1831 self.send_header('Content-Type', 'text/html')
1832 self.send_header('Content-Length', len(raw_reply))
1833 self.end_headers()
1834 self.wfile.write(raw_reply)
1835 return True;
1836
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001837 def ChromiumSyncTransientErrorOpHandler(self):
1838 test_name = "/chromiumsync/transienterror"
1839 if not self._ShouldHandleRequest(test_name):
1840 return False
1841 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1842 self.send_response(result)
1843 self.send_header('Content-Type', 'text/html')
1844 self.send_header('Content-Length', len(raw_reply))
1845 self.end_headers()
1846 self.wfile.write(raw_reply)
1847 return True;
1848
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001849 def ChromiumSyncErrorOpHandler(self):
1850 test_name = "/chromiumsync/error"
1851 if not self._ShouldHandleRequest(test_name):
1852 return False
1853 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1854 self.path)
1855 self.send_response(result)
1856 self.send_header('Content-Type', 'text/html')
1857 self.send_header('Content-Length', len(raw_reply))
1858 self.end_headers()
1859 self.wfile.write(raw_reply)
1860 return True;
1861
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001862 def ChromiumSyncSyncTabsOpHandler(self):
1863 test_name = "/chromiumsync/synctabs"
1864 if not self._ShouldHandleRequest(test_name):
1865 return False
1866 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1867 self.send_response(result)
1868 self.send_header('Content-Type', 'text/html')
1869 self.send_header('Content-Length', len(raw_reply))
1870 self.end_headers()
1871 self.wfile.write(raw_reply)
1872 return True;
1873
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001874 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1875 test_name = "/chromiumsync/createsyncedbookmarks"
1876 if not self._ShouldHandleRequest(test_name):
1877 return False
1878 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1879 self.send_response(result)
1880 self.send_header('Content-Type', 'text/html')
1881 self.send_header('Content-Length', len(raw_reply))
1882 self.end_headers()
1883 self.wfile.write(raw_reply)
1884 return True;
1885
akalin@chromium.org154bb132010-11-12 02:20:27 +00001886
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001887def MakeDataDir():
1888 if options.data_dir:
1889 if not os.path.isdir(options.data_dir):
1890 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1891 return None
1892 my_data_dir = options.data_dir
1893 else:
1894 # Create the default path to our data dir, relative to the exe dir.
1895 my_data_dir = os.path.dirname(sys.argv[0])
1896 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001897 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001898
1899 #TODO(ibrar): Must use Find* funtion defined in google\tools
1900 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1901
1902 return my_data_dir
1903
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001904class OCSPHandler(BasePageHandler):
1905 def __init__(self, request, client_address, socket_server):
1906 handlers = [self.OCSPResponse]
1907 self.ocsp_response = socket_server.ocsp_response
1908 BasePageHandler.__init__(self, request, client_address, socket_server,
1909 [], handlers, [], handlers, [])
1910
1911 def OCSPResponse(self):
1912 self.send_response(200)
1913 self.send_header('Content-Type', 'application/ocsp-response')
1914 self.send_header('Content-Length', str(len(self.ocsp_response)))
1915 self.end_headers()
1916
1917 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001918
1919class TCPEchoHandler(SocketServer.BaseRequestHandler):
1920 """The RequestHandler class for TCP echo server.
1921
1922 It is instantiated once per connection to the server, and overrides the
1923 handle() method to implement communication to the client.
1924 """
1925
1926 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001927 """Handles the request from the client and constructs a response."""
1928
1929 data = self.request.recv(65536).strip()
1930 # Verify the "echo request" message received from the client. Send back
1931 # "echo response" message if "echo request" message is valid.
1932 try:
1933 return_data = echo_message.GetEchoResponseData(data)
1934 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001935 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001936 except ValueError:
1937 return
1938
1939 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001940
1941
1942class UDPEchoHandler(SocketServer.BaseRequestHandler):
1943 """The RequestHandler class for UDP echo server.
1944
1945 It is instantiated once per connection to the server, and overrides the
1946 handle() method to implement communication to the client.
1947 """
1948
1949 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001950 """Handles the request from the client and constructs a response."""
1951
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001952 data = self.request[0].strip()
1953 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001954 # Verify the "echo request" message received from the client. Send back
1955 # "echo response" message if "echo request" message is valid.
1956 try:
1957 return_data = echo_message.GetEchoResponseData(data)
1958 if not return_data:
1959 return
1960 except ValueError:
1961 return
1962 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001963
1964
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001965class FileMultiplexer:
1966 def __init__(self, fd1, fd2) :
1967 self.__fd1 = fd1
1968 self.__fd2 = fd2
1969
1970 def __del__(self) :
1971 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1972 self.__fd1.close()
1973 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1974 self.__fd2.close()
1975
1976 def write(self, text) :
1977 self.__fd1.write(text)
1978 self.__fd2.write(text)
1979
1980 def flush(self) :
1981 self.__fd1.flush()
1982 self.__fd2.flush()
1983
initial.commit94958cf2008-07-26 22:42:52 +00001984def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00001985 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001986 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00001987 if options.log_to_console:
1988 sys.stdout = FileMultiplexer(sys.stdout, logfile)
1989 else:
1990 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00001991
1992 port = options.port
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001993 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00001994
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001995 server_data = {}
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001996 server_data['host'] = host
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00001997
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001998 ocsp_server = None
1999
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002000 if options.server_type == SERVER_HTTP:
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002001 if options.https:
2002 pem_cert_and_key = None
2003 if options.cert_and_key_file:
2004 if not os.path.isfile(options.cert_and_key_file):
2005 print ('specified server cert file not found: ' +
2006 options.cert_and_key_file + ' exiting...')
2007 return
2008 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
2009 else:
2010 # generate a new certificate and run an OCSP server for it.
2011 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2012 print ('OCSP server started on %s:%d...' %
2013 (host, ocsp_server.server_port))
2014
2015 ocsp_der = None
2016 ocsp_revoked = False
2017 ocsp_invalid = False
2018
2019 if options.ocsp == 'ok':
2020 pass
2021 elif options.ocsp == 'revoked':
2022 ocsp_revoked = True
2023 elif options.ocsp == 'invalid':
2024 ocsp_invalid = True
2025 else:
2026 print 'unknown OCSP status: ' + options.ocsp_status
2027 return
2028
2029 (pem_cert_and_key, ocsp_der) = \
2030 minica.GenerateCertKeyAndOCSP(
2031 subject = "127.0.0.1",
2032 ocsp_url = ("http://%s:%d/ocsp" %
2033 (host, ocsp_server.server_port)),
2034 ocsp_revoked = ocsp_revoked)
2035
2036 if ocsp_invalid:
2037 ocsp_der = '3'
2038
2039 ocsp_server.ocsp_response = ocsp_der
2040
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002041 for ca_cert in options.ssl_client_ca:
2042 if not os.path.isfile(ca_cert):
2043 print 'specified trusted client CA file not found: ' + ca_cert + \
2044 ' exiting...'
2045 return
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002046 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002047 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002048 options.ssl_bulk_cipher, options.record_resume)
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002049 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002050 else:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002051 server = HTTPServer((host, port), TestPageHandler)
2052 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002053
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002054 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002055 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002056 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00002057 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002058 server.policy_keys = options.policy_keys
2059 server.policy_user = options.policy_user
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002060 server.gdata_auth_token = options.auth_token
akalin@chromium.org154bb132010-11-12 02:20:27 +00002061 elif options.server_type == SERVER_SYNC:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002062 server = SyncHTTPServer((host, port), SyncPageHandler)
akalin@chromium.org154bb132010-11-12 02:20:27 +00002063 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002064 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2065 server_data['port'] = server.server_port
2066 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002067 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002068 # Used for generating the key (randomly) that encodes the "echo request"
2069 # message.
2070 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002071 server = TCPEchoServer((host, port), TCPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002072 print 'Echo TCP server started on port %d...' % server.server_port
2073 server_data['port'] = server.server_port
2074 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002075 # Used for generating the key (randomly) that encodes the "echo request"
2076 # message.
2077 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002078 server = UDPEchoServer((host, port), UDPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002079 print 'Echo UDP server started on port %d...' % server.server_port
2080 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002081 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00002082 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002083 my_data_dir = MakeDataDir()
2084
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002085 # Instantiate a dummy authorizer for managing 'virtual' users
2086 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2087
2088 # Define a new user having full r/w permissions and a read-only
2089 # anonymous user
2090 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2091
2092 authorizer.add_anonymous(my_data_dir)
2093
2094 # Instantiate FTP handler class
2095 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2096 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002097
2098 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00002099 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2100 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002101
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002102 # Instantiate FTP server class and listen to address:port
2103 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002104 server_data['port'] = server.socket.getsockname()[1]
2105 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00002106
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002107 # Notify the parent that we've started. (BaseServer subclasses
2108 # bind their sockets on construction.)
2109 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00002110 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002111 server_data_len = len(server_data_json)
2112 print 'sending server_data: %s (%d bytes)' % (
2113 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002114 if sys.platform == 'win32':
2115 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2116 else:
2117 fd = options.startup_pipe
2118 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002119 # First write the data length as an unsigned 4-byte value. This
2120 # is _not_ using network byte ordering since the other end of the
2121 # pipe is on the same machine.
2122 startup_pipe.write(struct.pack('=L', server_data_len))
2123 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002124 startup_pipe.close()
2125
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002126 if ocsp_server is not None:
2127 ocsp_server.serve_forever_on_thread()
2128
initial.commit94958cf2008-07-26 22:42:52 +00002129 try:
2130 server.serve_forever()
2131 except KeyboardInterrupt:
2132 print 'shutting down server'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002133 if ocsp_server is not None:
2134 ocsp_server.stop_serving()
initial.commit94958cf2008-07-26 22:42:52 +00002135 server.stop = True
2136
2137if __name__ == '__main__':
2138 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002139 option_parser.add_option("-f", '--ftp', action='store_const',
2140 const=SERVER_FTP, default=SERVER_HTTP,
2141 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00002142 help='start up an FTP server.')
2143 option_parser.add_option('', '--sync', action='store_const',
2144 const=SERVER_SYNC, default=SERVER_HTTP,
2145 dest='server_type',
2146 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002147 option_parser.add_option('', '--tcp-echo', action='store_const',
2148 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2149 dest='server_type',
2150 help='start up a tcp echo server.')
2151 option_parser.add_option('', '--udp-echo', action='store_const',
2152 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2153 dest='server_type',
2154 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002155 option_parser.add_option('', '--log-to-console', action='store_const',
2156 const=True, default=False,
2157 dest='log_to_console',
2158 help='Enables or disables sys.stdout logging to '
2159 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00002160 option_parser.add_option('', '--port', default='0', type='int',
2161 help='Port used by the server. If unspecified, the '
2162 'server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002163 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002164 help='Directory from which to read the files.')
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002165 option_parser.add_option('', '--https', action='store_true', dest='https',
2166 help='Specify that https should be used.')
2167 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2168 help='specify the path to the file containing the '
2169 'certificate and private key for the server in PEM '
2170 'format')
2171 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2172 help='The type of OCSP response generated for the '
2173 'automatically generated certificate. One of '
2174 '[ok,revoked,invalid]')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002175 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2176 const=True, default=False, action='store_const',
2177 help='Record resumption cache events rather than'
2178 ' resuming as normal. Allows the use of the'
2179 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002180 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2181 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002182 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2183 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002184 'should include the CA named in the subject of '
2185 'the DER-encoded certificate contained in the '
2186 'specified file. This option may appear multiple '
2187 'times, indicating multiple CA names should be '
2188 'sent in the request.')
2189 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2190 help='Specify the bulk encryption algorithm(s)'
2191 'that will be accepted by the SSL server. Valid '
2192 'values are "aes256", "aes128", "3des", "rc4". If '
2193 'omitted, all algorithms will be used. This '
2194 'option may appear multiple times, indicating '
2195 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002196 option_parser.add_option('', '--file-root-url', default='/files/',
2197 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002198 option_parser.add_option('', '--startup-pipe', type='int',
2199 dest='startup_pipe',
2200 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002201 option_parser.add_option('', '--policy-key', action='append',
2202 dest='policy_keys',
2203 help='Specify a path to a PEM-encoded private key '
2204 'to use for policy signing. May be specified '
2205 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002206 'the server. If ther server has multiple keys, it '
2207 'will rotate through them in at each request a '
2208 'round-robin fashion. The server will generate a '
2209 'random key if none is specified on the command '
2210 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002211 option_parser.add_option('', '--policy-user', default='user@example.com',
2212 dest='policy_user',
2213 help='Specify the user name the server should '
2214 'report back to the client as the user owning the '
2215 'token used for making the policy request.')
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002216 option_parser.add_option('', '--host', default='127.0.0.1',
2217 dest='host',
2218 help='Hostname or IP upon which the server will '
2219 'listen. Client connections will also only be '
2220 'allowed from this address.')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002221 option_parser.add_option('', '--auth-token', dest='auth_token',
2222 help='Specify the auth token which should be used'
2223 'in the authorization header for GData.')
initial.commit94958cf2008-07-26 22:42:52 +00002224 options, args = option_parser.parse_args()
2225
2226 sys.exit(main(options, args))