blob: c96623bab3f3a6681b4340c9e2d51044d089958e [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,
agl@chromium.org143daa42012-04-26 18:45:34 +0000136 record_resume_info, tls_intolerant):
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 = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000141 self.tls_intolerant = tls_intolerant
142
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000143 for ca_file in ssl_client_cas:
144 s = open(ca_file).read()
145 x509 = tlslite.api.X509()
146 x509.parse(s)
147 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000148 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
149 if ssl_bulk_ciphers is not None:
150 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000151
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000152 if record_resume_info:
153 # If record_resume_info is true then we'll replace the session cache with
154 # an object that records the lookups and inserts that it sees.
155 self.session_cache = RecordingSSLSessionCache()
156 else:
157 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000158 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
159
160 def handshake(self, tlsConnection):
161 """Creates the SSL connection."""
162 try:
163 tlsConnection.handshakeServer(certChain=self.cert_chain,
164 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000165 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000166 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000167 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000168 reqCAs=self.ssl_client_cas,
169 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000170 tlsConnection.ignoreAbruptClose = True
171 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000172 except tlslite.api.TLSAbruptCloseError:
173 # Ignore abrupt close.
174 return True
initial.commit94958cf2008-07-26 22:42:52 +0000175 except tlslite.api.TLSError, error:
176 print "Handshake failure:", str(error)
177 return False
178
akalin@chromium.org154bb132010-11-12 02:20:27 +0000179
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000180class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000181 """An HTTP server that handles sync commands."""
182
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000183 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000184 # We import here to avoid pulling in chromiumsync's dependencies
185 # unless strictly necessary.
186 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000187 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000188 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000189 self._sync_handler = chromiumsync.TestServer()
190 self._xmpp_socket_map = {}
191 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000192 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000193 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000194 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000195
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000196 def GetXmppServer(self):
197 return self._xmpp_server
198
akalin@chromium.org154bb132010-11-12 02:20:27 +0000199 def HandleCommand(self, query, raw_request):
200 return self._sync_handler.HandleCommand(query, raw_request)
201
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000202 def HandleRequestNoBlock(self):
203 """Handles a single request.
204
205 Copied from SocketServer._handle_request_noblock().
206 """
207 try:
208 request, client_address = self.get_request()
209 except socket.error:
210 return
211 if self.verify_request(request, client_address):
212 try:
213 self.process_request(request, client_address)
214 except:
215 self.handle_error(request, client_address)
216 self.close_request(request)
217
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000218 def SetAuthenticated(self, auth_valid):
219 self.authenticated = auth_valid
220
221 def GetAuthenticated(self):
222 return self.authenticated
223
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000224 def serve_forever(self):
225 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
226 """
227
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000228 def HandleXmppSocket(fd, socket_map, handler):
229 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000230
231 Adapted from asyncore.read() et al.
232 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000233 xmpp_connection = socket_map.get(fd)
234 # This could happen if a previous handler call caused fd to get
235 # removed from socket_map.
236 if xmpp_connection is None:
237 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000238 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000239 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000240 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
241 raise
242 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000243 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000244
245 while True:
246 read_fds = [ self.fileno() ]
247 write_fds = []
248 exceptional_fds = []
249
250 for fd, xmpp_connection in self._xmpp_socket_map.items():
251 is_r = xmpp_connection.readable()
252 is_w = xmpp_connection.writable()
253 if is_r:
254 read_fds.append(fd)
255 if is_w:
256 write_fds.append(fd)
257 if is_r or is_w:
258 exceptional_fds.append(fd)
259
260 try:
261 read_fds, write_fds, exceptional_fds = (
262 select.select(read_fds, write_fds, exceptional_fds))
263 except select.error, err:
264 if err.args[0] != errno.EINTR:
265 raise
266 else:
267 continue
268
269 for fd in read_fds:
270 if fd == self.fileno():
271 self.HandleRequestNoBlock()
272 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000273 HandleXmppSocket(fd, self._xmpp_socket_map,
274 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000275
276 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000277 HandleXmppSocket(fd, self._xmpp_socket_map,
278 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000279
280 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000281 HandleXmppSocket(fd, self._xmpp_socket_map,
282 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000283
akalin@chromium.org154bb132010-11-12 02:20:27 +0000284
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000285class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
286 """This is a specialization of FTPServer that adds client verification."""
287
288 pass
289
290
291class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000292 """A TCP echo server that echoes back what it has received."""
293
294 def server_bind(self):
295 """Override server_bind to store the server name."""
296 SocketServer.TCPServer.server_bind(self)
297 host, port = self.socket.getsockname()[:2]
298 self.server_name = socket.getfqdn(host)
299 self.server_port = port
300
301 def serve_forever(self):
302 self.stop = False
303 self.nonce_time = None
304 while not self.stop:
305 self.handle_request()
306 self.socket.close()
307
308
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000309class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000310 """A UDP echo server that echoes back what it has received."""
311
312 def server_bind(self):
313 """Override server_bind to store the server name."""
314 SocketServer.UDPServer.server_bind(self)
315 host, port = self.socket.getsockname()[:2]
316 self.server_name = socket.getfqdn(host)
317 self.server_port = port
318
319 def serve_forever(self):
320 self.stop = False
321 self.nonce_time = None
322 while not self.stop:
323 self.handle_request()
324 self.socket.close()
325
326
akalin@chromium.org154bb132010-11-12 02:20:27 +0000327class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
328
329 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000330 connect_handlers, get_handlers, head_handlers, post_handlers,
331 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000332 self._connect_handlers = connect_handlers
333 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000334 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000335 self._post_handlers = post_handlers
336 self._put_handlers = put_handlers
337 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
338 self, request, client_address, socket_server)
339
340 def log_request(self, *args, **kwargs):
341 # Disable request logging to declutter test log output.
342 pass
343
344 def _ShouldHandleRequest(self, handler_name):
345 """Determines if the path can be handled by the handler.
346
347 We consider a handler valid if the path begins with the
348 handler name. It can optionally be followed by "?*", "/*".
349 """
350
351 pattern = re.compile('%s($|\?|/).*' % handler_name)
352 return pattern.match(self.path)
353
354 def do_CONNECT(self):
355 for handler in self._connect_handlers:
356 if handler():
357 return
358
359 def do_GET(self):
360 for handler in self._get_handlers:
361 if handler():
362 return
363
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000364 def do_HEAD(self):
365 for handler in self._head_handlers:
366 if handler():
367 return
368
akalin@chromium.org154bb132010-11-12 02:20:27 +0000369 def do_POST(self):
370 for handler in self._post_handlers:
371 if handler():
372 return
373
374 def do_PUT(self):
375 for handler in self._put_handlers:
376 if handler():
377 return
378
379
380class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000381
382 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000383 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000384 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000385 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000386 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000387 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000388 self.NoCacheMaxAgeTimeHandler,
389 self.NoCacheTimeHandler,
390 self.CacheTimeHandler,
391 self.CacheExpiresHandler,
392 self.CacheProxyRevalidateHandler,
393 self.CachePrivateHandler,
394 self.CachePublicHandler,
395 self.CacheSMaxAgeHandler,
396 self.CacheMustRevalidateHandler,
397 self.CacheMustRevalidateMaxAgeHandler,
398 self.CacheNoStoreHandler,
399 self.CacheNoStoreMaxAgeHandler,
400 self.CacheNoTransformHandler,
401 self.DownloadHandler,
402 self.DownloadFinishHandler,
403 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000404 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000405 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000406 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000407 self.GDataAuthHandler,
408 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000410 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000411 self.SetManyCookiesHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000412 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.AuthBasicHandler,
414 self.AuthDigestHandler,
415 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000416 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000417 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000418 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000419 self.ServerRedirectHandler,
420 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000421 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000422 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000423 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000424 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000425 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000426 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000427 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000428 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000429 self.DeviceManagementHandler,
430 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000431 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000432 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000433 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000434 head_handlers = [
435 self.FileHandler,
436 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000437
maruel@google.come250a9b2009-03-10 17:39:46 +0000438 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000439 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000440 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000441 'gif': 'image/gif',
442 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000443 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000444 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000445 'pdf' : 'application/pdf',
446 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000447 }
initial.commit94958cf2008-07-26 22:42:52 +0000448 self._default_mime_type = 'text/html'
449
akalin@chromium.org154bb132010-11-12 02:20:27 +0000450 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000451 connect_handlers, get_handlers, head_handlers,
452 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000453
initial.commit94958cf2008-07-26 22:42:52 +0000454 def GetMIMETypeFromName(self, file_name):
455 """Returns the mime type for the specified file_name. So far it only looks
456 at the file extension."""
457
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000458 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000459 if len(extension) == 0:
460 # no extension.
461 return self._default_mime_type
462
ericroman@google.comc17ca532009-05-07 03:51:05 +0000463 # extension starts with a dot, so we need to remove it
464 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000465
initial.commit94958cf2008-07-26 22:42:52 +0000466 def NoCacheMaxAgeTimeHandler(self):
467 """This request handler yields a page with the title set to the current
468 system time, and no caching requested."""
469
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000470 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000471 return False
472
473 self.send_response(200)
474 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000475 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000476 self.end_headers()
477
maruel@google.come250a9b2009-03-10 17:39:46 +0000478 self.wfile.write('<html><head><title>%s</title></head></html>' %
479 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000480
481 return True
482
483 def NoCacheTimeHandler(self):
484 """This request handler yields a page with the title set to the current
485 system time, and no caching requested."""
486
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000487 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000488 return False
489
490 self.send_response(200)
491 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000492 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000493 self.end_headers()
494
maruel@google.come250a9b2009-03-10 17:39:46 +0000495 self.wfile.write('<html><head><title>%s</title></head></html>' %
496 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000497
498 return True
499
500 def CacheTimeHandler(self):
501 """This request handler yields a page with the title set to the current
502 system time, and allows caching for one minute."""
503
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000504 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000505 return False
506
507 self.send_response(200)
508 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000509 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000510 self.end_headers()
511
maruel@google.come250a9b2009-03-10 17:39:46 +0000512 self.wfile.write('<html><head><title>%s</title></head></html>' %
513 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000514
515 return True
516
517 def CacheExpiresHandler(self):
518 """This request handler yields a page with the title set to the current
519 system time, and set the page to expire on 1 Jan 2099."""
520
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000521 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000522 return False
523
524 self.send_response(200)
525 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000526 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000527 self.end_headers()
528
maruel@google.come250a9b2009-03-10 17:39:46 +0000529 self.wfile.write('<html><head><title>%s</title></head></html>' %
530 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000531
532 return True
533
534 def CacheProxyRevalidateHandler(self):
535 """This request handler yields a page with the title set to the current
536 system time, and allows caching for 60 seconds"""
537
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000538 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000539 return False
540
541 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000542 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000543 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
544 self.end_headers()
545
maruel@google.come250a9b2009-03-10 17:39:46 +0000546 self.wfile.write('<html><head><title>%s</title></head></html>' %
547 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000548
549 return True
550
551 def CachePrivateHandler(self):
552 """This request handler yields a page with the title set to the current
553 system time, and allows caching for 5 seconds."""
554
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000555 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000556 return False
557
558 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000559 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000560 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000561 self.end_headers()
562
maruel@google.come250a9b2009-03-10 17:39:46 +0000563 self.wfile.write('<html><head><title>%s</title></head></html>' %
564 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000565
566 return True
567
568 def CachePublicHandler(self):
569 """This request handler yields a page with the title set to the current
570 system time, and allows caching for 5 seconds."""
571
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000572 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000573 return False
574
575 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000576 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000577 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000578 self.end_headers()
579
maruel@google.come250a9b2009-03-10 17:39:46 +0000580 self.wfile.write('<html><head><title>%s</title></head></html>' %
581 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000582
583 return True
584
585 def CacheSMaxAgeHandler(self):
586 """This request handler yields a page with the title set to the current
587 system time, and does not allow for caching."""
588
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000589 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000590 return False
591
592 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000593 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000594 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
595 self.end_headers()
596
maruel@google.come250a9b2009-03-10 17:39:46 +0000597 self.wfile.write('<html><head><title>%s</title></head></html>' %
598 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000599
600 return True
601
602 def CacheMustRevalidateHandler(self):
603 """This request handler yields a page with the title set to the current
604 system time, and does not allow caching."""
605
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000606 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000607 return False
608
609 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000610 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000611 self.send_header('Cache-Control', 'must-revalidate')
612 self.end_headers()
613
maruel@google.come250a9b2009-03-10 17:39:46 +0000614 self.wfile.write('<html><head><title>%s</title></head></html>' %
615 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000616
617 return True
618
619 def CacheMustRevalidateMaxAgeHandler(self):
620 """This request handler yields a page with the title set to the current
621 system time, and does not allow caching event though max-age of 60
622 seconds is specified."""
623
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000624 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000625 return False
626
627 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000628 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000629 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
630 self.end_headers()
631
maruel@google.come250a9b2009-03-10 17:39:46 +0000632 self.wfile.write('<html><head><title>%s</title></head></html>' %
633 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000634
635 return True
636
initial.commit94958cf2008-07-26 22:42:52 +0000637 def CacheNoStoreHandler(self):
638 """This request handler yields a page with the title set to the current
639 system time, and does not allow the page to be stored."""
640
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000641 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000642 return False
643
644 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000645 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000646 self.send_header('Cache-Control', 'no-store')
647 self.end_headers()
648
maruel@google.come250a9b2009-03-10 17:39:46 +0000649 self.wfile.write('<html><head><title>%s</title></head></html>' %
650 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000651
652 return True
653
654 def CacheNoStoreMaxAgeHandler(self):
655 """This request handler yields a page with the title set to the current
656 system time, and does not allow the page to be stored even though max-age
657 of 60 seconds is specified."""
658
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000659 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000660 return False
661
662 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000663 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000664 self.send_header('Cache-Control', 'max-age=60, no-store')
665 self.end_headers()
666
maruel@google.come250a9b2009-03-10 17:39:46 +0000667 self.wfile.write('<html><head><title>%s</title></head></html>' %
668 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000669
670 return True
671
672
673 def CacheNoTransformHandler(self):
674 """This request handler yields a page with the title set to the current
675 system time, and does not allow the content to transformed during
676 user-agent caching"""
677
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000678 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000679 return False
680
681 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000682 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000683 self.send_header('Cache-Control', 'no-transform')
684 self.end_headers()
685
maruel@google.come250a9b2009-03-10 17:39:46 +0000686 self.wfile.write('<html><head><title>%s</title></head></html>' %
687 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000688
689 return True
690
691 def EchoHeader(self):
692 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000693 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000694
ananta@chromium.org56812d02011-04-07 17:52:05 +0000695 """This function echoes back the value of a specific request header"""
696 """while allowing caching for 16 hours."""
697 def EchoHeaderCache(self):
698 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000699
700 def EchoHeaderHelper(self, echo_header):
701 """This function echoes back the value of the request header passed in."""
702 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000703 return False
704
705 query_char = self.path.find('?')
706 if query_char != -1:
707 header_name = self.path[query_char+1:]
708
709 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000710 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000711 if echo_header == '/echoheadercache':
712 self.send_header('Cache-control', 'max-age=60000')
713 else:
714 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000715 # insert a vary header to properly indicate that the cachability of this
716 # request is subject to value of the request header being echoed.
717 if len(header_name) > 0:
718 self.send_header('Vary', header_name)
719 self.end_headers()
720
721 if len(header_name) > 0:
722 self.wfile.write(self.headers.getheader(header_name))
723
724 return True
725
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000726 def ReadRequestBody(self):
727 """This function reads the body of the current HTTP request, handling
728 both plain and chunked transfer encoded requests."""
729
730 if self.headers.getheader('transfer-encoding') != 'chunked':
731 length = int(self.headers.getheader('content-length'))
732 return self.rfile.read(length)
733
734 # Read the request body as chunks.
735 body = ""
736 while True:
737 line = self.rfile.readline()
738 length = int(line, 16)
739 if length == 0:
740 self.rfile.readline()
741 break
742 body += self.rfile.read(length)
743 self.rfile.read(2)
744 return body
745
initial.commit94958cf2008-07-26 22:42:52 +0000746 def EchoHandler(self):
747 """This handler just echoes back the payload of the request, for testing
748 form submission."""
749
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000750 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000751 return False
752
753 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000754 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000755 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000756 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000757 return True
758
759 def EchoTitleHandler(self):
760 """This handler is like Echo, but sets the page title to the request."""
761
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000762 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000763 return False
764
765 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000766 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000767 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000768 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000769 self.wfile.write('<html><head><title>')
770 self.wfile.write(request)
771 self.wfile.write('</title></head></html>')
772 return True
773
774 def EchoAllHandler(self):
775 """This handler yields a (more) human-readable page listing information
776 about the request header & contents."""
777
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000778 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000779 return False
780
781 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000782 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000783 self.end_headers()
784 self.wfile.write('<html><head><style>'
785 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
786 '</style></head><body>'
787 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000788 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000789 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000790
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000791 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000792 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000793 params = cgi.parse_qs(qs, keep_blank_values=1)
794
795 for param in params:
796 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000797
798 self.wfile.write('</pre>')
799
800 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
801
802 self.wfile.write('</body></html>')
803 return True
804
805 def DownloadHandler(self):
806 """This handler sends a downloadable file with or without reporting
807 the size (6K)."""
808
809 if self.path.startswith("/download-unknown-size"):
810 send_length = False
811 elif self.path.startswith("/download-known-size"):
812 send_length = True
813 else:
814 return False
815
816 #
817 # The test which uses this functionality is attempting to send
818 # small chunks of data to the client. Use a fairly large buffer
819 # so that we'll fill chrome's IO buffer enough to force it to
820 # actually write the data.
821 # See also the comments in the client-side of this test in
822 # download_uitest.cc
823 #
824 size_chunk1 = 35*1024
825 size_chunk2 = 10*1024
826
827 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000828 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000829 self.send_header('Cache-Control', 'max-age=0')
830 if send_length:
831 self.send_header('Content-Length', size_chunk1 + size_chunk2)
832 self.end_headers()
833
834 # First chunk of data:
835 self.wfile.write("*" * size_chunk1)
836 self.wfile.flush()
837
838 # handle requests until one of them clears this flag.
839 self.server.waitForDownload = True
840 while self.server.waitForDownload:
841 self.server.handle_request()
842
843 # Second chunk of data:
844 self.wfile.write("*" * size_chunk2)
845 return True
846
847 def DownloadFinishHandler(self):
848 """This handler just tells the server to finish the current download."""
849
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000850 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000851 return False
852
853 self.server.waitForDownload = False
854 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000855 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000856 self.send_header('Cache-Control', 'max-age=0')
857 self.end_headers()
858 return True
859
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000860 def _ReplaceFileData(self, data, query_parameters):
861 """Replaces matching substrings in a file.
862
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000863 If the 'replace_text' URL query parameter is present, it is expected to be
864 of the form old_text:new_text, which indicates that any old_text strings in
865 the file are replaced with new_text. Multiple 'replace_text' parameters may
866 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000867
868 If the parameters are not present, |data| is returned.
869 """
870 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000871 replace_text_values = query_dict.get('replace_text', [])
872 for replace_text_value in replace_text_values:
873 replace_text_args = replace_text_value.split(':')
874 if len(replace_text_args) != 2:
875 raise ValueError(
876 'replace_text must be of form old_text:new_text. Actual value: %s' %
877 replace_text_value)
878 old_text_b64, new_text_b64 = replace_text_args
879 old_text = base64.urlsafe_b64decode(old_text_b64)
880 new_text = base64.urlsafe_b64decode(new_text_b64)
881 data = data.replace(old_text, new_text)
882 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000883
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000884 def ZipFileHandler(self):
885 """This handler sends the contents of the requested file in compressed form.
886 Can pass in a parameter that specifies that the content length be
887 C - the compressed size (OK),
888 U - the uncompressed size (Non-standard, but handled),
889 S - less than compressed (OK because we keep going),
890 M - larger than compressed but less than uncompressed (an error),
891 L - larger than uncompressed (an error)
892 Example: compressedfiles/Picture_1.doc?C
893 """
894
895 prefix = "/compressedfiles/"
896 if not self.path.startswith(prefix):
897 return False
898
899 # Consume a request body if present.
900 if self.command == 'POST' or self.command == 'PUT' :
901 self.ReadRequestBody()
902
903 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
904
905 if not query in ('C', 'U', 'S', 'M', 'L'):
906 return False
907
908 sub_path = url_path[len(prefix):]
909 entries = sub_path.split('/')
910 file_path = os.path.join(self.server.data_dir, *entries)
911 if os.path.isdir(file_path):
912 file_path = os.path.join(file_path, 'index.html')
913
914 if not os.path.isfile(file_path):
915 print "File not found " + sub_path + " full path:" + file_path
916 self.send_error(404)
917 return True
918
919 f = open(file_path, "rb")
920 data = f.read()
921 uncompressed_len = len(data)
922 f.close()
923
924 # Compress the data.
925 data = zlib.compress(data)
926 compressed_len = len(data)
927
928 content_length = compressed_len
929 if query == 'U':
930 content_length = uncompressed_len
931 elif query == 'S':
932 content_length = compressed_len / 2
933 elif query == 'M':
934 content_length = (compressed_len + uncompressed_len) / 2
935 elif query == 'L':
936 content_length = compressed_len + uncompressed_len
937
938 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000939 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000940 self.send_header('Content-encoding', 'deflate')
941 self.send_header('Connection', 'close')
942 self.send_header('Content-Length', content_length)
943 self.send_header('ETag', '\'' + file_path + '\'')
944 self.end_headers()
945
946 self.wfile.write(data)
947
948 return True
949
initial.commit94958cf2008-07-26 22:42:52 +0000950 def FileHandler(self):
951 """This handler sends the contents of the requested file. Wow, it's like
952 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000953 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000954 if not self.path.startswith(prefix):
955 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000956 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000957
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000958 def PostOnlyFileHandler(self):
959 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000960 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000961 if not self.path.startswith(prefix):
962 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000963 return self._FileHandlerHelper(prefix)
964
965 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000966 request_body = ''
967 if self.command == 'POST' or self.command == 'PUT':
968 # Consume a request body if present.
969 request_body = self.ReadRequestBody()
970
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000971 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000972 query_dict = cgi.parse_qs(query)
973
974 expected_body = query_dict.get('expected_body', [])
975 if expected_body and request_body not in expected_body:
976 self.send_response(404)
977 self.end_headers()
978 self.wfile.write('')
979 return True
980
981 expected_headers = query_dict.get('expected_headers', [])
982 for expected_header in expected_headers:
983 header_name, expected_value = expected_header.split(':')
984 if self.headers.getheader(header_name) != expected_value:
985 self.send_response(404)
986 self.end_headers()
987 self.wfile.write('')
988 return True
989
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000990 sub_path = url_path[len(prefix):]
991 entries = sub_path.split('/')
992 file_path = os.path.join(self.server.data_dir, *entries)
993 if os.path.isdir(file_path):
994 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000995
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000996 if not os.path.isfile(file_path):
997 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000998 self.send_error(404)
999 return True
1000
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001001 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001002 data = f.read()
1003 f.close()
1004
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001005 data = self._ReplaceFileData(data, query)
1006
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001007 old_protocol_version = self.protocol_version
1008
initial.commit94958cf2008-07-26 22:42:52 +00001009 # If file.mock-http-headers exists, it contains the headers we
1010 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001011 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001012 if os.path.isfile(headers_path):
1013 f = open(headers_path, "r")
1014
1015 # "HTTP/1.1 200 OK"
1016 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001017 http_major, http_minor, status_code = re.findall(
1018 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1019 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001020 self.send_response(int(status_code))
1021
1022 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001023 header_values = re.findall('(\S+):\s*(.*)', line)
1024 if len(header_values) > 0:
1025 # "name: value"
1026 name, value = header_values[0]
1027 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001028 f.close()
1029 else:
1030 # Could be more generic once we support mime-type sniffing, but for
1031 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001032
1033 range = self.headers.get('Range')
1034 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001035 # Note this doesn't handle all valid byte range values (i.e. left
1036 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001037 range = range[6:].split('-')
1038 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001039 if range[1]:
1040 end = int(range[1])
1041 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001042 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001043
1044 self.send_response(206)
1045 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1046 str(len(data))
1047 self.send_header('Content-Range', content_range)
1048 data = data[start: end + 1]
1049 else:
1050 self.send_response(200)
1051
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001052 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001053 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001054 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001055 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001056 self.end_headers()
1057
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001058 if (self.command != 'HEAD'):
1059 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001060
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001061 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001062 return True
1063
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001064 def SetCookieHandler(self):
1065 """This handler just sets a cookie, for testing cookie handling."""
1066
1067 if not self._ShouldHandleRequest("/set-cookie"):
1068 return False
1069
1070 query_char = self.path.find('?')
1071 if query_char != -1:
1072 cookie_values = self.path[query_char + 1:].split('&')
1073 else:
1074 cookie_values = ("",)
1075 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001076 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001077 for cookie_value in cookie_values:
1078 self.send_header('Set-Cookie', '%s' % cookie_value)
1079 self.end_headers()
1080 for cookie_value in cookie_values:
1081 self.wfile.write('%s' % cookie_value)
1082 return True
1083
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001084 def SetManyCookiesHandler(self):
1085 """This handler just sets a given number of cookies, for testing handling
1086 of large numbers of cookies."""
1087
1088 if not self._ShouldHandleRequest("/set-many-cookies"):
1089 return False
1090
1091 query_char = self.path.find('?')
1092 if query_char != -1:
1093 num_cookies = int(self.path[query_char + 1:])
1094 else:
1095 num_cookies = 0
1096 self.send_response(200)
1097 self.send_header('', 'text/html')
1098 for i in range(0, num_cookies):
1099 self.send_header('Set-Cookie', 'a=')
1100 self.end_headers()
1101 self.wfile.write('%d cookies were sent' % num_cookies)
1102 return True
1103
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001104 def SetHeaderHandler(self):
1105 """This handler sets a response header. Parameters are in the
1106 key%3A%20value&key2%3A%20value2 format."""
1107
1108 if not self._ShouldHandleRequest("/set-header"):
1109 return False
1110
1111 query_char = self.path.find('?')
1112 if query_char != -1:
1113 headers_values = self.path[query_char + 1:].split('&')
1114 else:
1115 headers_values = ("",)
1116 self.send_response(200)
1117 self.send_header('Content-Type', 'text/html')
1118 for header_value in headers_values:
1119 header_value = urllib.unquote(header_value)
1120 (key, value) = header_value.split(': ', 1)
1121 self.send_header(key, value)
1122 self.end_headers()
1123 for header_value in headers_values:
1124 self.wfile.write('%s' % header_value)
1125 return True
1126
initial.commit94958cf2008-07-26 22:42:52 +00001127 def AuthBasicHandler(self):
1128 """This handler tests 'Basic' authentication. It just sends a page with
1129 title 'user/pass' if you succeed."""
1130
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001131 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001132 return False
1133
1134 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001135 expected_password = 'secret'
1136 realm = 'testrealm'
1137 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001138
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001139 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1140 query_params = cgi.parse_qs(query, True)
1141 if 'set-cookie-if-challenged' in query_params:
1142 set_cookie_if_challenged = True
1143 if 'password' in query_params:
1144 expected_password = query_params['password'][0]
1145 if 'realm' in query_params:
1146 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001147
initial.commit94958cf2008-07-26 22:42:52 +00001148 auth = self.headers.getheader('authorization')
1149 try:
1150 if not auth:
1151 raise Exception('no auth')
1152 b64str = re.findall(r'Basic (\S+)', auth)[0]
1153 userpass = base64.b64decode(b64str)
1154 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001155 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001156 raise Exception('wrong password')
1157 except Exception, e:
1158 # Authentication failed.
1159 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001160 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001161 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001162 if set_cookie_if_challenged:
1163 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001164 self.end_headers()
1165 self.wfile.write('<html><head>')
1166 self.wfile.write('<title>Denied: %s</title>' % e)
1167 self.wfile.write('</head><body>')
1168 self.wfile.write('auth=%s<p>' % auth)
1169 self.wfile.write('b64str=%s<p>' % b64str)
1170 self.wfile.write('username: %s<p>' % username)
1171 self.wfile.write('userpass: %s<p>' % userpass)
1172 self.wfile.write('password: %s<p>' % password)
1173 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1174 self.wfile.write('</body></html>')
1175 return True
1176
1177 # Authentication successful. (Return a cachable response to allow for
1178 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001179 old_protocol_version = self.protocol_version
1180 self.protocol_version = "HTTP/1.1"
1181
initial.commit94958cf2008-07-26 22:42:52 +00001182 if_none_match = self.headers.getheader('if-none-match')
1183 if if_none_match == "abc":
1184 self.send_response(304)
1185 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001186 elif url_path.endswith(".gif"):
1187 # Using chrome/test/data/google/logo.gif as the test image
1188 test_image_path = ['google', 'logo.gif']
1189 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1190 if not os.path.isfile(gif_path):
1191 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001192 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001193 return True
1194
1195 f = open(gif_path, "rb")
1196 data = f.read()
1197 f.close()
1198
1199 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001200 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001201 self.send_header('Cache-control', 'max-age=60000')
1202 self.send_header('Etag', 'abc')
1203 self.end_headers()
1204 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001205 else:
1206 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001207 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001208 self.send_header('Cache-control', 'max-age=60000')
1209 self.send_header('Etag', 'abc')
1210 self.end_headers()
1211 self.wfile.write('<html><head>')
1212 self.wfile.write('<title>%s/%s</title>' % (username, password))
1213 self.wfile.write('</head><body>')
1214 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001215 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001216 self.wfile.write('</body></html>')
1217
rvargas@google.com54453b72011-05-19 01:11:11 +00001218 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001219 return True
1220
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001221 def GDataAuthHandler(self):
1222 """This handler verifies the Authentication header for GData requests."""
1223 if not self.server.gdata_auth_token:
1224 # --auth-token is not specified, not the test case for GData.
1225 return False
1226
1227 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1228 return False
1229
1230 if 'GData-Version' not in self.headers:
1231 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1232 return True
1233
1234 if 'Authorization' not in self.headers:
1235 self.send_error(httplib.UNAUTHORIZED)
1236 return True
1237
1238 field_prefix = 'Bearer '
1239 authorization = self.headers['Authorization']
1240 if not authorization.startswith(field_prefix):
1241 self.send_error(httplib.UNAUTHORIZED)
1242 return True
1243
1244 code = authorization[len(field_prefix):]
1245 if code != self.server.gdata_auth_token:
1246 self.send_error(httplib.UNAUTHORIZED)
1247 return True
1248
1249 return False
1250
1251 def GDataDocumentsFeedQueryHandler(self):
1252 """This handler verifies if required parameters are properly
1253 specified for the GData DocumentsFeed request."""
1254 if not self.server.gdata_auth_token:
1255 # --auth-token is not specified, not the test case for GData.
1256 return False
1257
1258 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1259 return False
1260
1261 (path, question, query_params) = self.path.partition('?')
1262 self.query_params = urlparse.parse_qs(query_params)
1263
1264 if 'v' not in self.query_params:
1265 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1266 return True
1267 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1268 # currently our GData client only uses JSON format.
1269 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1270 return True
1271
1272 return False
1273
tonyg@chromium.org75054202010-03-31 22:06:10 +00001274 def GetNonce(self, force_reset=False):
1275 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001276
tonyg@chromium.org75054202010-03-31 22:06:10 +00001277 This is a fake implementation. A real implementation would only use a given
1278 nonce a single time (hence the name n-once). However, for the purposes of
1279 unittesting, we don't care about the security of the nonce.
1280
1281 Args:
1282 force_reset: Iff set, the nonce will be changed. Useful for testing the
1283 "stale" response.
1284 """
1285 if force_reset or not self.server.nonce_time:
1286 self.server.nonce_time = time.time()
1287 return _new_md5('privatekey%s%d' %
1288 (self.path, self.server.nonce_time)).hexdigest()
1289
1290 def AuthDigestHandler(self):
1291 """This handler tests 'Digest' authentication.
1292
1293 It just sends a page with title 'user/pass' if you succeed.
1294
1295 A stale response is sent iff "stale" is present in the request path.
1296 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001297 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001298 return False
1299
tonyg@chromium.org75054202010-03-31 22:06:10 +00001300 stale = 'stale' in self.path
1301 nonce = self.GetNonce(force_reset=stale)
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001302 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001303 password = 'secret'
1304 realm = 'testrealm'
1305
1306 auth = self.headers.getheader('authorization')
1307 pairs = {}
1308 try:
1309 if not auth:
1310 raise Exception('no auth')
1311 if not auth.startswith('Digest'):
1312 raise Exception('not digest')
1313 # Pull out all the name="value" pairs as a dictionary.
1314 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1315
1316 # Make sure it's all valid.
1317 if pairs['nonce'] != nonce:
1318 raise Exception('wrong nonce')
1319 if pairs['opaque'] != opaque:
1320 raise Exception('wrong opaque')
1321
1322 # Check the 'response' value and make sure it matches our magic hash.
1323 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +00001324 hash_a1 = _new_md5(
1325 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001326 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001327 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001328 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001329 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1330 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +00001331 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001332
1333 if pairs['response'] != response:
1334 raise Exception('wrong password')
1335 except Exception, e:
1336 # Authentication failed.
1337 self.send_response(401)
1338 hdr = ('Digest '
1339 'realm="%s", '
1340 'domain="/", '
1341 'qop="auth", '
1342 'algorithm=MD5, '
1343 'nonce="%s", '
1344 'opaque="%s"') % (realm, nonce, opaque)
1345 if stale:
1346 hdr += ', stale="TRUE"'
1347 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001348 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001349 self.end_headers()
1350 self.wfile.write('<html><head>')
1351 self.wfile.write('<title>Denied: %s</title>' % e)
1352 self.wfile.write('</head><body>')
1353 self.wfile.write('auth=%s<p>' % auth)
1354 self.wfile.write('pairs=%s<p>' % pairs)
1355 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1356 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1357 self.wfile.write('</body></html>')
1358 return True
1359
1360 # Authentication successful.
1361 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001362 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001363 self.end_headers()
1364 self.wfile.write('<html><head>')
1365 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1366 self.wfile.write('</head><body>')
1367 self.wfile.write('auth=%s<p>' % auth)
1368 self.wfile.write('pairs=%s<p>' % pairs)
1369 self.wfile.write('</body></html>')
1370
1371 return True
1372
1373 def SlowServerHandler(self):
1374 """Wait for the user suggested time before responding. The syntax is
1375 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001376 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001377 return False
1378 query_char = self.path.find('?')
1379 wait_sec = 1.0
1380 if query_char >= 0:
1381 try:
1382 wait_sec = int(self.path[query_char + 1:])
1383 except ValueError:
1384 pass
1385 time.sleep(wait_sec)
1386 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001387 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001388 self.end_headers()
1389 self.wfile.write("waited %d seconds" % wait_sec)
1390 return True
1391
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001392 def ChunkedServerHandler(self):
1393 """Send chunked response. Allows to specify chunks parameters:
1394 - waitBeforeHeaders - ms to wait before sending headers
1395 - waitBetweenChunks - ms to wait between chunks
1396 - chunkSize - size of each chunk in bytes
1397 - chunksNumber - number of chunks
1398 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1399 waits one second, then sends headers and five chunks five bytes each."""
1400 if not self._ShouldHandleRequest("/chunked"):
1401 return False
1402 query_char = self.path.find('?')
1403 chunkedSettings = {'waitBeforeHeaders' : 0,
1404 'waitBetweenChunks' : 0,
1405 'chunkSize' : 5,
1406 'chunksNumber' : 5}
1407 if query_char >= 0:
1408 params = self.path[query_char + 1:].split('&')
1409 for param in params:
1410 keyValue = param.split('=')
1411 if len(keyValue) == 2:
1412 try:
1413 chunkedSettings[keyValue[0]] = int(keyValue[1])
1414 except ValueError:
1415 pass
1416 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1417 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1418 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001419 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001420 self.send_header('Connection', 'close')
1421 self.send_header('Transfer-Encoding', 'chunked')
1422 self.end_headers()
1423 # Chunked encoding: sending all chunks, then final zero-length chunk and
1424 # then final CRLF.
1425 for i in range(0, chunkedSettings['chunksNumber']):
1426 if i > 0:
1427 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1428 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1429 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1430 self.sendChunkHelp('')
1431 return True
1432
initial.commit94958cf2008-07-26 22:42:52 +00001433 def ContentTypeHandler(self):
1434 """Returns a string of html with the given content type. E.g.,
1435 /contenttype?text/css returns an html file with the Content-Type
1436 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001437 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001438 return False
1439 query_char = self.path.find('?')
1440 content_type = self.path[query_char + 1:].strip()
1441 if not content_type:
1442 content_type = 'text/html'
1443 self.send_response(200)
1444 self.send_header('Content-Type', content_type)
1445 self.end_headers()
1446 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1447 return True
1448
creis@google.com2f4f6a42011-03-25 19:44:19 +00001449 def NoContentHandler(self):
1450 """Returns a 204 No Content response."""
1451 if not self._ShouldHandleRequest("/nocontent"):
1452 return False
1453 self.send_response(204)
1454 self.end_headers()
1455 return True
1456
initial.commit94958cf2008-07-26 22:42:52 +00001457 def ServerRedirectHandler(self):
1458 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001459 '/server-redirect?http://foo.bar/asdf' to redirect to
1460 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001461
1462 test_name = "/server-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(301) # moved permanently
1473 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001474 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001475 self.end_headers()
1476 self.wfile.write('<html><head>')
1477 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1478
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001479 return True
initial.commit94958cf2008-07-26 22:42:52 +00001480
1481 def ClientRedirectHandler(self):
1482 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001483 '/client-redirect?http://foo.bar/asdf' to redirect to
1484 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001485
1486 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001487 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001488 return False
1489
1490 query_char = self.path.find('?');
1491 if query_char < 0 or len(self.path) <= query_char + 1:
1492 self.sendRedirectHelp(test_name)
1493 return True
1494 dest = self.path[query_char + 1:]
1495
1496 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001497 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001498 self.end_headers()
1499 self.wfile.write('<html><head>')
1500 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1501 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1502
1503 return True
1504
tony@chromium.org03266982010-03-05 03:18:42 +00001505 def MultipartHandler(self):
1506 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001507 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001508 if not self._ShouldHandleRequest(test_name):
1509 return False
1510
1511 num_frames = 10
1512 bound = '12345'
1513 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001514 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +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.org03266982010-03-05 03:18:42 +00001521 self.wfile.write('<title>page ' + str(i) + '</title>')
1522 self.wfile.write('page ' + str(i))
1523
1524 self.wfile.write('--' + bound + '--')
1525 return True
1526
tony@chromium.org4cb88302011-09-27 22:13:49 +00001527 def MultipartSlowHandler(self):
1528 """Send a multipart response (3 text/html pages) with a slight delay
1529 between each page. This is similar to how some pages show status using
1530 multipart."""
1531 test_name = '/multipart-slow'
1532 if not self._ShouldHandleRequest(test_name):
1533 return False
1534
1535 num_frames = 3
1536 bound = '12345'
1537 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001538 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001539 'multipart/x-mixed-replace;boundary=' + bound)
1540 self.end_headers()
1541
1542 for i in xrange(num_frames):
1543 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001544 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001545 time.sleep(0.25)
1546 if i == 2:
1547 self.wfile.write('<title>PASS</title>')
1548 else:
1549 self.wfile.write('<title>page ' + str(i) + '</title>')
1550 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1551
1552 self.wfile.write('--' + bound + '--')
1553 return True
1554
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001555 def GetSSLSessionCacheHandler(self):
1556 """Send a reply containing a log of the session cache operations."""
1557
1558 if not self._ShouldHandleRequest('/ssl-session-cache'):
1559 return False
1560
1561 self.send_response(200)
1562 self.send_header('Content-Type', 'text/plain')
1563 self.end_headers()
1564 try:
1565 for (action, sessionID) in self.server.session_cache.log:
1566 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1567 except AttributeError, e:
1568 self.wfile.write('Pass --https-record-resume in order to use' +
1569 ' this request')
1570 return True
1571
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001572 def CloseSocketHandler(self):
1573 """Closes the socket without sending anything."""
1574
1575 if not self._ShouldHandleRequest('/close-socket'):
1576 return False
1577
1578 self.wfile.close()
1579 return True
1580
initial.commit94958cf2008-07-26 22:42:52 +00001581 def DefaultResponseHandler(self):
1582 """This is the catch-all response handler for requests that aren't handled
1583 by one of the special handlers above.
1584 Note that we specify the content-length as without it the https connection
1585 is not closed properly (and the browser keeps expecting data)."""
1586
1587 contents = "Default response given for path: " + self.path
1588 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001589 self.send_header('Content-Type', 'text/html')
1590 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001591 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001592 if (self.command != 'HEAD'):
1593 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001594 return True
1595
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001596 def RedirectConnectHandler(self):
1597 """Sends a redirect to the CONNECT request for www.redirect.com. This
1598 response is not specified by the RFC, so the browser should not follow
1599 the redirect."""
1600
1601 if (self.path.find("www.redirect.com") < 0):
1602 return False
1603
1604 dest = "http://www.destination.com/foo.js"
1605
1606 self.send_response(302) # moved temporarily
1607 self.send_header('Location', dest)
1608 self.send_header('Connection', 'close')
1609 self.end_headers()
1610 return True
1611
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001612 def ServerAuthConnectHandler(self):
1613 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1614 response doesn't make sense because the proxy server cannot request
1615 server authentication."""
1616
1617 if (self.path.find("www.server-auth.com") < 0):
1618 return False
1619
1620 challenge = 'Basic realm="WallyWorld"'
1621
1622 self.send_response(401) # unauthorized
1623 self.send_header('WWW-Authenticate', challenge)
1624 self.send_header('Connection', 'close')
1625 self.end_headers()
1626 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001627
1628 def DefaultConnectResponseHandler(self):
1629 """This is the catch-all response handler for CONNECT requests that aren't
1630 handled by one of the special handlers above. Real Web servers respond
1631 with 400 to CONNECT requests."""
1632
1633 contents = "Your client has issued a malformed or illegal request."
1634 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001635 self.send_header('Content-Type', 'text/html')
1636 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001637 self.end_headers()
1638 self.wfile.write(contents)
1639 return True
1640
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001641 def DeviceManagementHandler(self):
1642 """Delegates to the device management service used for cloud policy."""
1643 if not self._ShouldHandleRequest("/device_management"):
1644 return False
1645
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001646 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001647
1648 if not self.server._device_management_handler:
1649 import device_management
1650 policy_path = os.path.join(self.server.data_dir, 'device_management')
1651 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001652 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001653 self.server.policy_keys,
1654 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001655
1656 http_response, raw_reply = (
1657 self.server._device_management_handler.HandleRequest(self.path,
1658 self.headers,
1659 raw_request))
1660 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001661 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001663 self.end_headers()
1664 self.wfile.write(raw_reply)
1665 return True
1666
initial.commit94958cf2008-07-26 22:42:52 +00001667 # called by the redirect handling function when there is no parameter
1668 def sendRedirectHelp(self, redirect_name):
1669 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001670 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001671 self.end_headers()
1672 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1673 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1674 self.wfile.write('</body></html>')
1675
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001676 # called by chunked handling function
1677 def sendChunkHelp(self, chunk):
1678 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1679 self.wfile.write('%X\r\n' % len(chunk))
1680 self.wfile.write(chunk)
1681 self.wfile.write('\r\n')
1682
akalin@chromium.org154bb132010-11-12 02:20:27 +00001683
1684class SyncPageHandler(BasePageHandler):
1685 """Handler for the main HTTP sync server."""
1686
1687 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001688 get_handlers = [self.ChromiumSyncTimeHandler,
1689 self.ChromiumSyncMigrationOpHandler,
1690 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001691 self.ChromiumSyncDisableNotificationsOpHandler,
1692 self.ChromiumSyncEnableNotificationsOpHandler,
1693 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001694 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001695 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001696 self.ChromiumSyncErrorOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001697 self.ChromiumSyncSyncTabsOpHandler,
1698 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001699
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001700 post_handlers = [self.ChromiumSyncCommandHandler,
1701 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001702 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001703 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001704 post_handlers, [])
1705
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001706
akalin@chromium.org154bb132010-11-12 02:20:27 +00001707 def ChromiumSyncTimeHandler(self):
1708 """Handle Chromium sync .../time requests.
1709
1710 The syncer sometimes checks server reachability by examining /time.
1711 """
1712 test_name = "/chromiumsync/time"
1713 if not self._ShouldHandleRequest(test_name):
1714 return False
1715
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001716 # Chrome hates it if we send a response before reading the request.
1717 if self.headers.getheader('content-length'):
1718 length = int(self.headers.getheader('content-length'))
1719 raw_request = self.rfile.read(length)
1720
akalin@chromium.org154bb132010-11-12 02:20:27 +00001721 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001722 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001723 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001724 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001725 return True
1726
1727 def ChromiumSyncCommandHandler(self):
1728 """Handle a chromiumsync command arriving via http.
1729
1730 This covers all sync protocol commands: authentication, getupdates, and
1731 commit.
1732 """
1733 test_name = "/chromiumsync/command"
1734 if not self._ShouldHandleRequest(test_name):
1735 return False
1736
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001737 length = int(self.headers.getheader('content-length'))
1738 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001739 http_response = 200
1740 raw_reply = None
1741 if not self.server.GetAuthenticated():
1742 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001743 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1744 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001745 else:
1746 http_response, raw_reply = self.server.HandleCommand(
1747 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001748
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001749 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001750 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001751 if http_response == 401:
1752 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001753 self.end_headers()
1754 self.wfile.write(raw_reply)
1755 return True
1756
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001757 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001758 test_name = "/chromiumsync/migrate"
1759 if not self._ShouldHandleRequest(test_name):
1760 return False
1761
1762 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1763 self.path)
1764 self.send_response(http_response)
1765 self.send_header('Content-Type', 'text/html')
1766 self.send_header('Content-Length', len(raw_reply))
1767 self.end_headers()
1768 self.wfile.write(raw_reply)
1769 return True
1770
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001771 def ChromiumSyncCredHandler(self):
1772 test_name = "/chromiumsync/cred"
1773 if not self._ShouldHandleRequest(test_name):
1774 return False
1775 try:
1776 query = urlparse.urlparse(self.path)[4]
1777 cred_valid = urlparse.parse_qs(query)['valid']
1778 if cred_valid[0] == 'True':
1779 self.server.SetAuthenticated(True)
1780 else:
1781 self.server.SetAuthenticated(False)
1782 except:
1783 self.server.SetAuthenticated(False)
1784
1785 http_response = 200
1786 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1787 self.send_response(http_response)
1788 self.send_header('Content-Type', 'text/html')
1789 self.send_header('Content-Length', len(raw_reply))
1790 self.end_headers()
1791 self.wfile.write(raw_reply)
1792 return True
1793
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001794 def ChromiumSyncDisableNotificationsOpHandler(self):
1795 test_name = "/chromiumsync/disablenotifications"
1796 if not self._ShouldHandleRequest(test_name):
1797 return False
1798 self.server.GetXmppServer().DisableNotifications()
1799 result = 200
1800 raw_reply = ('<html><title>Notifications disabled</title>'
1801 '<H1>Notifications disabled</H1></html>')
1802 self.send_response(result)
1803 self.send_header('Content-Type', 'text/html')
1804 self.send_header('Content-Length', len(raw_reply))
1805 self.end_headers()
1806 self.wfile.write(raw_reply)
1807 return True;
1808
1809 def ChromiumSyncEnableNotificationsOpHandler(self):
1810 test_name = "/chromiumsync/enablenotifications"
1811 if not self._ShouldHandleRequest(test_name):
1812 return False
1813 self.server.GetXmppServer().EnableNotifications()
1814 result = 200
1815 raw_reply = ('<html><title>Notifications enabled</title>'
1816 '<H1>Notifications enabled</H1></html>')
1817 self.send_response(result)
1818 self.send_header('Content-Type', 'text/html')
1819 self.send_header('Content-Length', len(raw_reply))
1820 self.end_headers()
1821 self.wfile.write(raw_reply)
1822 return True;
1823
1824 def ChromiumSyncSendNotificationOpHandler(self):
1825 test_name = "/chromiumsync/sendnotification"
1826 if not self._ShouldHandleRequest(test_name):
1827 return False
1828 query = urlparse.urlparse(self.path)[4]
1829 query_params = urlparse.parse_qs(query)
1830 channel = ''
1831 data = ''
1832 if 'channel' in query_params:
1833 channel = query_params['channel'][0]
1834 if 'data' in query_params:
1835 data = query_params['data'][0]
1836 self.server.GetXmppServer().SendNotification(channel, data)
1837 result = 200
1838 raw_reply = ('<html><title>Notification sent</title>'
1839 '<H1>Notification sent with channel "%s" '
1840 'and data "%s"</H1></html>'
1841 % (channel, data))
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.orgb56c12b2011-07-30 02:17:37 +00001849 def ChromiumSyncBirthdayErrorOpHandler(self):
1850 test_name = "/chromiumsync/birthdayerror"
1851 if not self._ShouldHandleRequest(test_name):
1852 return False
1853 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1854 self.send_response(result)
1855 self.send_header('Content-Type', 'text/html')
1856 self.send_header('Content-Length', len(raw_reply))
1857 self.end_headers()
1858 self.wfile.write(raw_reply)
1859 return True;
1860
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001861 def ChromiumSyncTransientErrorOpHandler(self):
1862 test_name = "/chromiumsync/transienterror"
1863 if not self._ShouldHandleRequest(test_name):
1864 return False
1865 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1866 self.send_response(result)
1867 self.send_header('Content-Type', 'text/html')
1868 self.send_header('Content-Length', len(raw_reply))
1869 self.end_headers()
1870 self.wfile.write(raw_reply)
1871 return True;
1872
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001873 def ChromiumSyncErrorOpHandler(self):
1874 test_name = "/chromiumsync/error"
1875 if not self._ShouldHandleRequest(test_name):
1876 return False
1877 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1878 self.path)
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
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001886 def ChromiumSyncSyncTabsOpHandler(self):
1887 test_name = "/chromiumsync/synctabs"
1888 if not self._ShouldHandleRequest(test_name):
1889 return False
1890 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1891 self.send_response(result)
1892 self.send_header('Content-Type', 'text/html')
1893 self.send_header('Content-Length', len(raw_reply))
1894 self.end_headers()
1895 self.wfile.write(raw_reply)
1896 return True;
1897
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001898 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1899 test_name = "/chromiumsync/createsyncedbookmarks"
1900 if not self._ShouldHandleRequest(test_name):
1901 return False
1902 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1903 self.send_response(result)
1904 self.send_header('Content-Type', 'text/html')
1905 self.send_header('Content-Length', len(raw_reply))
1906 self.end_headers()
1907 self.wfile.write(raw_reply)
1908 return True;
1909
akalin@chromium.org154bb132010-11-12 02:20:27 +00001910
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001911def MakeDataDir():
1912 if options.data_dir:
1913 if not os.path.isdir(options.data_dir):
1914 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1915 return None
1916 my_data_dir = options.data_dir
1917 else:
1918 # Create the default path to our data dir, relative to the exe dir.
1919 my_data_dir = os.path.dirname(sys.argv[0])
1920 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +00001921 "test", "data")
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001922
1923 #TODO(ibrar): Must use Find* funtion defined in google\tools
1924 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1925
1926 return my_data_dir
1927
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001928class OCSPHandler(BasePageHandler):
1929 def __init__(self, request, client_address, socket_server):
1930 handlers = [self.OCSPResponse]
1931 self.ocsp_response = socket_server.ocsp_response
1932 BasePageHandler.__init__(self, request, client_address, socket_server,
1933 [], handlers, [], handlers, [])
1934
1935 def OCSPResponse(self):
1936 self.send_response(200)
1937 self.send_header('Content-Type', 'application/ocsp-response')
1938 self.send_header('Content-Length', str(len(self.ocsp_response)))
1939 self.end_headers()
1940
1941 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001942
1943class TCPEchoHandler(SocketServer.BaseRequestHandler):
1944 """The RequestHandler class for TCP echo server.
1945
1946 It is instantiated once per connection to the server, and overrides the
1947 handle() method to implement communication to the client.
1948 """
1949
1950 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001951 """Handles the request from the client and constructs a response."""
1952
1953 data = self.request.recv(65536).strip()
1954 # 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:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001959 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001960 except ValueError:
1961 return
1962
1963 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001964
1965
1966class UDPEchoHandler(SocketServer.BaseRequestHandler):
1967 """The RequestHandler class for UDP echo server.
1968
1969 It is instantiated once per connection to the server, and overrides the
1970 handle() method to implement communication to the client.
1971 """
1972
1973 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001974 """Handles the request from the client and constructs a response."""
1975
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001976 data = self.request[0].strip()
1977 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001978 # Verify the "echo request" message received from the client. Send back
1979 # "echo response" message if "echo request" message is valid.
1980 try:
1981 return_data = echo_message.GetEchoResponseData(data)
1982 if not return_data:
1983 return
1984 except ValueError:
1985 return
1986 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001987
1988
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001989class FileMultiplexer:
1990 def __init__(self, fd1, fd2) :
1991 self.__fd1 = fd1
1992 self.__fd2 = fd2
1993
1994 def __del__(self) :
1995 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
1996 self.__fd1.close()
1997 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
1998 self.__fd2.close()
1999
2000 def write(self, text) :
2001 self.__fd1.write(text)
2002 self.__fd2.write(text)
2003
2004 def flush(self) :
2005 self.__fd1.flush()
2006 self.__fd2.flush()
2007
initial.commit94958cf2008-07-26 22:42:52 +00002008def main(options, args):
initial.commit94958cf2008-07-26 22:42:52 +00002009 logfile = open('testserver.log', 'w')
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002010 sys.stderr = FileMultiplexer(sys.stderr, logfile)
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002011 if options.log_to_console:
2012 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2013 else:
2014 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002015
2016 port = options.port
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002017 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002018
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002019 server_data = {}
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002020 server_data['host'] = host
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002021
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002022 ocsp_server = None
2023
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002024 if options.server_type == SERVER_HTTP:
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002025 if options.https:
2026 pem_cert_and_key = None
2027 if options.cert_and_key_file:
2028 if not os.path.isfile(options.cert_and_key_file):
2029 print ('specified server cert file not found: ' +
2030 options.cert_and_key_file + ' exiting...')
2031 return
2032 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
2033 else:
2034 # generate a new certificate and run an OCSP server for it.
2035 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2036 print ('OCSP server started on %s:%d...' %
2037 (host, ocsp_server.server_port))
2038
2039 ocsp_der = None
agl@chromium.orgb5f388a2012-06-29 17:54:06 +00002040 ocsp_state = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002041
2042 if options.ocsp == 'ok':
agl@chromium.orgb5f388a2012-06-29 17:54:06 +00002043 ocsp_state = minica.OCSP_STATE_GOOD
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002044 elif options.ocsp == 'revoked':
agl@chromium.orgb5f388a2012-06-29 17:54:06 +00002045 ocsp_state = minica.OCSP_STATE_REVOKED
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002046 elif options.ocsp == 'invalid':
agl@chromium.orgb5f388a2012-06-29 17:54:06 +00002047 ocsp_state = minica.OCSP_STATE_INVALID
2048 elif options.ocsp == 'unauthorized':
2049 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2050 elif options.ocsp == 'unknown':
2051 ocsp_state = minica.OCSP_STATE_UNKNOWN
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002052 else:
2053 print 'unknown OCSP status: ' + options.ocsp_status
2054 return
2055
2056 (pem_cert_and_key, ocsp_der) = \
2057 minica.GenerateCertKeyAndOCSP(
2058 subject = "127.0.0.1",
2059 ocsp_url = ("http://%s:%d/ocsp" %
2060 (host, ocsp_server.server_port)),
agl@chromium.orgb5f388a2012-06-29 17:54:06 +00002061 ocsp_state = ocsp_state)
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002062
2063 ocsp_server.ocsp_response = ocsp_der
2064
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002065 for ca_cert in options.ssl_client_ca:
2066 if not os.path.isfile(ca_cert):
2067 print 'specified trusted client CA file not found: ' + ca_cert + \
2068 ' exiting...'
2069 return
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002070 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002071 options.ssl_client_auth, options.ssl_client_ca,
agl@chromium.org143daa42012-04-26 18:45:34 +00002072 options.ssl_bulk_cipher, options.record_resume,
2073 options.tls_intolerant)
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002074 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002075 else:
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002076 server = HTTPServer((host, port), TestPageHandler)
2077 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002078
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002079 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002080 server.file_root_url = options.file_root_url
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002081 server_data['port'] = server.server_port
mnissler@chromium.org7c939802010-11-11 08:47:14 +00002082 server._device_management_handler = None
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002083 server.policy_keys = options.policy_keys
2084 server.policy_user = options.policy_user
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002085 server.gdata_auth_token = options.auth_token
akalin@chromium.org154bb132010-11-12 02:20:27 +00002086 elif options.server_type == SERVER_SYNC:
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +00002087 xmpp_port = options.xmpp_port
2088 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
akalin@chromium.org154bb132010-11-12 02:20:27 +00002089 print 'Sync HTTP server started on port %d...' % server.server_port
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002090 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2091 server_data['port'] = server.server_port
2092 server_data['xmpp_port'] = server.xmpp_port
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002093 elif options.server_type == SERVER_TCP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002094 # Used for generating the key (randomly) that encodes the "echo request"
2095 # message.
2096 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002097 server = TCPEchoServer((host, port), TCPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002098 print 'Echo TCP server started on port %d...' % server.server_port
2099 server_data['port'] = server.server_port
2100 elif options.server_type == SERVER_UDP_ECHO:
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002101 # Used for generating the key (randomly) that encodes the "echo request"
2102 # message.
2103 random.seed()
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002104 server = UDPEchoServer((host, port), UDPEchoHandler)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002105 print 'Echo UDP server started on port %d...' % server.server_port
2106 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002107 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00002108 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002109 my_data_dir = MakeDataDir()
2110
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002111 # Instantiate a dummy authorizer for managing 'virtual' users
2112 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2113
2114 # Define a new user having full r/w permissions and a read-only
2115 # anonymous user
2116 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2117
2118 authorizer.add_anonymous(my_data_dir)
2119
2120 # Instantiate FTP handler class
2121 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2122 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002123
2124 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00002125 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2126 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002127
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002128 # Instantiate FTP server class and listen to address:port
2129 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +00002130 server_data['port'] = server.socket.getsockname()[1]
2131 print 'FTP server started on port %d...' % server_data['port']
initial.commit94958cf2008-07-26 22:42:52 +00002132
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002133 # Notify the parent that we've started. (BaseServer subclasses
2134 # bind their sockets on construction.)
2135 if options.startup_pipe is not None:
dpranke@chromium.org70049b72011-10-14 00:38:18 +00002136 server_data_json = json.dumps(server_data)
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002137 server_data_len = len(server_data_json)
2138 print 'sending server_data: %s (%d bytes)' % (
2139 server_data_json, server_data_len)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002140 if sys.platform == 'win32':
2141 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2142 else:
2143 fd = options.startup_pipe
2144 startup_pipe = os.fdopen(fd, "w")
akalin@chromium.orgf8479c62010-11-27 01:52:52 +00002145 # First write the data length as an unsigned 4-byte value. This
2146 # is _not_ using network byte ordering since the other end of the
2147 # pipe is on the same machine.
2148 startup_pipe.write(struct.pack('=L', server_data_len))
2149 startup_pipe.write(server_data_json)
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002150 startup_pipe.close()
2151
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002152 if ocsp_server is not None:
2153 ocsp_server.serve_forever_on_thread()
2154
initial.commit94958cf2008-07-26 22:42:52 +00002155 try:
2156 server.serve_forever()
2157 except KeyboardInterrupt:
2158 print 'shutting down server'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002159 if ocsp_server is not None:
2160 ocsp_server.stop_serving()
initial.commit94958cf2008-07-26 22:42:52 +00002161 server.stop = True
2162
2163if __name__ == '__main__':
2164 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002165 option_parser.add_option("-f", '--ftp', action='store_const',
2166 const=SERVER_FTP, default=SERVER_HTTP,
2167 dest='server_type',
akalin@chromium.org154bb132010-11-12 02:20:27 +00002168 help='start up an FTP server.')
2169 option_parser.add_option('', '--sync', action='store_const',
2170 const=SERVER_SYNC, default=SERVER_HTTP,
2171 dest='server_type',
2172 help='start up a sync server.')
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002173 option_parser.add_option('', '--tcp-echo', action='store_const',
2174 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2175 dest='server_type',
2176 help='start up a tcp echo server.')
2177 option_parser.add_option('', '--udp-echo', action='store_const',
2178 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2179 dest='server_type',
2180 help='start up a udp echo server.')
rsimha@chromium.orge77acad2011-02-01 19:56:46 +00002181 option_parser.add_option('', '--log-to-console', action='store_const',
2182 const=True, default=False,
2183 dest='log_to_console',
2184 help='Enables or disables sys.stdout logging to '
2185 'the console.')
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00002186 option_parser.add_option('', '--port', default='0', type='int',
2187 help='Port used by the server. If unspecified, the '
2188 'server will listen on an ephemeral port.')
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +00002189 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2190 help='Port used by the XMPP server. If unspecified, '
2191 'the XMPP server will listen on an ephemeral port.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002192 option_parser.add_option('', '--data-dir', dest='data_dir',
robertshield@chromium.org5e231612010-01-20 18:23:53 +00002193 help='Directory from which to read the files.')
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002194 option_parser.add_option('', '--https', action='store_true', dest='https',
2195 help='Specify that https should be used.')
2196 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2197 help='specify the path to the file containing the '
2198 'certificate and private key for the server in PEM '
2199 'format')
2200 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2201 help='The type of OCSP response generated for the '
2202 'automatically generated certificate. One of '
2203 '[ok,revoked,invalid]')
agl@chromium.org143daa42012-04-26 18:45:34 +00002204 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
wtc@chromium.org8d0b5ee2012-05-23 18:32:23 +00002205 default='0', type='int',
2206 help='If nonzero, certain TLS connections will be'
2207 ' aborted in order to test version fallback. 1'
2208 ' means all TLS versions will be aborted. 2 means'
2209 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2210 ' 1.2 or higher will be aborted.')
agl@chromium.orgf9e66792011-12-12 22:22:19 +00002211 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2212 const=True, default=False, action='store_const',
2213 help='Record resumption cache events rather than'
2214 ' resuming as normal. Allows the use of the'
2215 ' /ssl-session-cache request')
davidben@chromium.org31282a12010-08-07 01:10:02 +00002216 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2217 help='Require SSL client auth on every connection.')
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +00002218 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2219 help='Specify that the client certificate request '
rsleevi@chromium.org2124c812010-10-28 11:57:36 +00002220 'should include the CA named in the subject of '
2221 'the DER-encoded certificate contained in the '
2222 'specified file. This option may appear multiple '
2223 'times, indicating multiple CA names should be '
2224 'sent in the request.')
2225 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2226 help='Specify the bulk encryption algorithm(s)'
2227 'that will be accepted by the SSL server. Valid '
2228 'values are "aes256", "aes128", "3des", "rc4". If '
2229 'omitted, all algorithms will be used. This '
2230 'option may appear multiple times, indicating '
2231 'multiple algorithms should be enabled.');
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00002232 option_parser.add_option('', '--file-root-url', default='/files/',
2233 help='Specify a root URL for files served.')
davidben@chromium.org06fcf202010-09-22 18:15:23 +00002234 option_parser.add_option('', '--startup-pipe', type='int',
2235 dest='startup_pipe',
2236 help='File handle of pipe to parent process')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002237 option_parser.add_option('', '--policy-key', action='append',
2238 dest='policy_keys',
2239 help='Specify a path to a PEM-encoded private key '
2240 'to use for policy signing. May be specified '
2241 'multiple times in order to load multipe keys into '
mnissler@chromium.org1655c712011-04-18 11:13:25 +00002242 'the server. If ther server has multiple keys, it '
2243 'will rotate through them in at each request a '
2244 'round-robin fashion. The server will generate a '
2245 'random key if none is specified on the command '
2246 'line.')
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00002247 option_parser.add_option('', '--policy-user', default='user@example.com',
2248 dest='policy_user',
2249 help='Specify the user name the server should '
2250 'report back to the client as the user owning the '
2251 'token used for making the policy request.')
erikwright@chromium.org847ef282012-02-22 16:41:10 +00002252 option_parser.add_option('', '--host', default='127.0.0.1',
2253 dest='host',
2254 help='Hostname or IP upon which the server will '
2255 'listen. Client connections will also only be '
2256 'allowed from this address.')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00002257 option_parser.add_option('', '--auth-token', dest='auth_token',
2258 help='Specify the auth token which should be used'
2259 'in the authorization header for GData.')
initial.commit94958cf2008-07-26 22:42:52 +00002260 options, args = option_parser.parse_args()
2261
2262 sys.exit(main(options, args))