blob: 84ee77ea1982e8dfcbab00104f9ce48927e90f18 [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
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000023import optparse
initial.commit94958cf2008-07-26 22:42:52 +000024import 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
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +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
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000036import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000039# Ignore deprecation warnings, they make our output more cluttered.
40warnings.filterwarnings("ignore", category=DeprecationWarning)
41
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
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000047try:
48 import hashlib
49 _new_md5 = hashlib.md5
50except ImportError:
51 import md5
52 _new_md5 = md5.new
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000053
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000054try:
55 import json
56except ImportError:
57 import simplejson as json
58
59if sys.platform == 'win32':
60 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000061
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,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000412 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000413 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000414 self.AuthBasicHandler,
415 self.AuthDigestHandler,
416 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000417 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000418 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000419 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000420 self.ServerRedirectHandler,
421 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000422 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000423 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000424 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000425 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000426 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000427 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000428 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000429 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000430 self.DeviceManagementHandler,
431 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000432 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000433 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000434 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000435 head_handlers = [
436 self.FileHandler,
437 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000438
maruel@google.come250a9b2009-03-10 17:39:46 +0000439 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000440 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000441 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000442 'gif': 'image/gif',
443 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000444 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000445 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000446 'pdf' : 'application/pdf',
447 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000448 }
initial.commit94958cf2008-07-26 22:42:52 +0000449 self._default_mime_type = 'text/html'
450
akalin@chromium.org154bb132010-11-12 02:20:27 +0000451 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000452 connect_handlers, get_handlers, head_handlers,
453 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000454
initial.commit94958cf2008-07-26 22:42:52 +0000455 def GetMIMETypeFromName(self, file_name):
456 """Returns the mime type for the specified file_name. So far it only looks
457 at the file extension."""
458
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000459 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000460 if len(extension) == 0:
461 # no extension.
462 return self._default_mime_type
463
ericroman@google.comc17ca532009-05-07 03:51:05 +0000464 # extension starts with a dot, so we need to remove it
465 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000466
initial.commit94958cf2008-07-26 22:42:52 +0000467 def NoCacheMaxAgeTimeHandler(self):
468 """This request handler yields a page with the title set to the current
469 system time, and no caching requested."""
470
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000471 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000472 return False
473
474 self.send_response(200)
475 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000476 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000477 self.end_headers()
478
maruel@google.come250a9b2009-03-10 17:39:46 +0000479 self.wfile.write('<html><head><title>%s</title></head></html>' %
480 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000481
482 return True
483
484 def NoCacheTimeHandler(self):
485 """This request handler yields a page with the title set to the current
486 system time, and no caching requested."""
487
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000488 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000489 return False
490
491 self.send_response(200)
492 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000493 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000494 self.end_headers()
495
maruel@google.come250a9b2009-03-10 17:39:46 +0000496 self.wfile.write('<html><head><title>%s</title></head></html>' %
497 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000498
499 return True
500
501 def CacheTimeHandler(self):
502 """This request handler yields a page with the title set to the current
503 system time, and allows caching for one minute."""
504
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000505 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000506 return False
507
508 self.send_response(200)
509 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000510 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000511 self.end_headers()
512
maruel@google.come250a9b2009-03-10 17:39:46 +0000513 self.wfile.write('<html><head><title>%s</title></head></html>' %
514 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000515
516 return True
517
518 def CacheExpiresHandler(self):
519 """This request handler yields a page with the title set to the current
520 system time, and set the page to expire on 1 Jan 2099."""
521
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000522 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000523 return False
524
525 self.send_response(200)
526 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000527 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000528 self.end_headers()
529
maruel@google.come250a9b2009-03-10 17:39:46 +0000530 self.wfile.write('<html><head><title>%s</title></head></html>' %
531 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000532
533 return True
534
535 def CacheProxyRevalidateHandler(self):
536 """This request handler yields a page with the title set to the current
537 system time, and allows caching for 60 seconds"""
538
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000539 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000540 return False
541
542 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000543 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000544 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
545 self.end_headers()
546
maruel@google.come250a9b2009-03-10 17:39:46 +0000547 self.wfile.write('<html><head><title>%s</title></head></html>' %
548 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000549
550 return True
551
552 def CachePrivateHandler(self):
553 """This request handler yields a page with the title set to the current
554 system time, and allows caching for 5 seconds."""
555
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000556 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000560 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000561 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000562 self.end_headers()
563
maruel@google.come250a9b2009-03-10 17:39:46 +0000564 self.wfile.write('<html><head><title>%s</title></head></html>' %
565 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000566
567 return True
568
569 def CachePublicHandler(self):
570 """This request handler yields a page with the title set to the current
571 system time, and allows caching for 5 seconds."""
572
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000573 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000574 return False
575
576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000577 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000578 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000579 self.end_headers()
580
maruel@google.come250a9b2009-03-10 17:39:46 +0000581 self.wfile.write('<html><head><title>%s</title></head></html>' %
582 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000583
584 return True
585
586 def CacheSMaxAgeHandler(self):
587 """This request handler yields a page with the title set to the current
588 system time, and does not allow for caching."""
589
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000590 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000591 return False
592
593 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000594 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000595 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
596 self.end_headers()
597
maruel@google.come250a9b2009-03-10 17:39:46 +0000598 self.wfile.write('<html><head><title>%s</title></head></html>' %
599 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000600
601 return True
602
603 def CacheMustRevalidateHandler(self):
604 """This request handler yields a page with the title set to the current
605 system time, and does not allow caching."""
606
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000607 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000608 return False
609
610 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000611 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000612 self.send_header('Cache-Control', 'must-revalidate')
613 self.end_headers()
614
maruel@google.come250a9b2009-03-10 17:39:46 +0000615 self.wfile.write('<html><head><title>%s</title></head></html>' %
616 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000617
618 return True
619
620 def CacheMustRevalidateMaxAgeHandler(self):
621 """This request handler yields a page with the title set to the current
622 system time, and does not allow caching event though max-age of 60
623 seconds is specified."""
624
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000625 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000626 return False
627
628 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000629 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000630 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
631 self.end_headers()
632
maruel@google.come250a9b2009-03-10 17:39:46 +0000633 self.wfile.write('<html><head><title>%s</title></head></html>' %
634 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000635
636 return True
637
initial.commit94958cf2008-07-26 22:42:52 +0000638 def CacheNoStoreHandler(self):
639 """This request handler yields a page with the title set to the current
640 system time, and does not allow the page to be stored."""
641
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000642 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000643 return False
644
645 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000646 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000647 self.send_header('Cache-Control', 'no-store')
648 self.end_headers()
649
maruel@google.come250a9b2009-03-10 17:39:46 +0000650 self.wfile.write('<html><head><title>%s</title></head></html>' %
651 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000652
653 return True
654
655 def CacheNoStoreMaxAgeHandler(self):
656 """This request handler yields a page with the title set to the current
657 system time, and does not allow the page to be stored even though max-age
658 of 60 seconds is specified."""
659
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000660 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000661 return False
662
663 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000664 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000665 self.send_header('Cache-Control', 'max-age=60, no-store')
666 self.end_headers()
667
maruel@google.come250a9b2009-03-10 17:39:46 +0000668 self.wfile.write('<html><head><title>%s</title></head></html>' %
669 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000670
671 return True
672
673
674 def CacheNoTransformHandler(self):
675 """This request handler yields a page with the title set to the current
676 system time, and does not allow the content to transformed during
677 user-agent caching"""
678
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000679 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000680 return False
681
682 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000683 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000684 self.send_header('Cache-Control', 'no-transform')
685 self.end_headers()
686
maruel@google.come250a9b2009-03-10 17:39:46 +0000687 self.wfile.write('<html><head><title>%s</title></head></html>' %
688 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000689
690 return True
691
692 def EchoHeader(self):
693 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000694 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000695
ananta@chromium.org56812d02011-04-07 17:52:05 +0000696 """This function echoes back the value of a specific request header"""
697 """while allowing caching for 16 hours."""
698 def EchoHeaderCache(self):
699 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000700
701 def EchoHeaderHelper(self, echo_header):
702 """This function echoes back the value of the request header passed in."""
703 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000704 return False
705
706 query_char = self.path.find('?')
707 if query_char != -1:
708 header_name = self.path[query_char+1:]
709
710 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000711 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000712 if echo_header == '/echoheadercache':
713 self.send_header('Cache-control', 'max-age=60000')
714 else:
715 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000716 # insert a vary header to properly indicate that the cachability of this
717 # request is subject to value of the request header being echoed.
718 if len(header_name) > 0:
719 self.send_header('Vary', header_name)
720 self.end_headers()
721
722 if len(header_name) > 0:
723 self.wfile.write(self.headers.getheader(header_name))
724
725 return True
726
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000727 def ReadRequestBody(self):
728 """This function reads the body of the current HTTP request, handling
729 both plain and chunked transfer encoded requests."""
730
731 if self.headers.getheader('transfer-encoding') != 'chunked':
732 length = int(self.headers.getheader('content-length'))
733 return self.rfile.read(length)
734
735 # Read the request body as chunks.
736 body = ""
737 while True:
738 line = self.rfile.readline()
739 length = int(line, 16)
740 if length == 0:
741 self.rfile.readline()
742 break
743 body += self.rfile.read(length)
744 self.rfile.read(2)
745 return body
746
initial.commit94958cf2008-07-26 22:42:52 +0000747 def EchoHandler(self):
748 """This handler just echoes back the payload of the request, for testing
749 form submission."""
750
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000751 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000752 return False
753
754 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000755 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000756 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000757 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000758 return True
759
760 def EchoTitleHandler(self):
761 """This handler is like Echo, but sets the page title to the request."""
762
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000763 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000764 return False
765
766 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000767 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000768 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000769 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000770 self.wfile.write('<html><head><title>')
771 self.wfile.write(request)
772 self.wfile.write('</title></head></html>')
773 return True
774
775 def EchoAllHandler(self):
776 """This handler yields a (more) human-readable page listing information
777 about the request header & contents."""
778
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000779 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000780 return False
781
782 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000783 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000784 self.end_headers()
785 self.wfile.write('<html><head><style>'
786 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
787 '</style></head><body>'
788 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000789 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000790 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000791
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000792 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000793 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000794 params = cgi.parse_qs(qs, keep_blank_values=1)
795
796 for param in params:
797 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000798
799 self.wfile.write('</pre>')
800
801 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
802
803 self.wfile.write('</body></html>')
804 return True
805
806 def DownloadHandler(self):
807 """This handler sends a downloadable file with or without reporting
808 the size (6K)."""
809
810 if self.path.startswith("/download-unknown-size"):
811 send_length = False
812 elif self.path.startswith("/download-known-size"):
813 send_length = True
814 else:
815 return False
816
817 #
818 # The test which uses this functionality is attempting to send
819 # small chunks of data to the client. Use a fairly large buffer
820 # so that we'll fill chrome's IO buffer enough to force it to
821 # actually write the data.
822 # See also the comments in the client-side of this test in
823 # download_uitest.cc
824 #
825 size_chunk1 = 35*1024
826 size_chunk2 = 10*1024
827
828 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000829 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000830 self.send_header('Cache-Control', 'max-age=0')
831 if send_length:
832 self.send_header('Content-Length', size_chunk1 + size_chunk2)
833 self.end_headers()
834
835 # First chunk of data:
836 self.wfile.write("*" * size_chunk1)
837 self.wfile.flush()
838
839 # handle requests until one of them clears this flag.
840 self.server.waitForDownload = True
841 while self.server.waitForDownload:
842 self.server.handle_request()
843
844 # Second chunk of data:
845 self.wfile.write("*" * size_chunk2)
846 return True
847
848 def DownloadFinishHandler(self):
849 """This handler just tells the server to finish the current download."""
850
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000851 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000852 return False
853
854 self.server.waitForDownload = False
855 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000856 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000857 self.send_header('Cache-Control', 'max-age=0')
858 self.end_headers()
859 return True
860
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000861 def _ReplaceFileData(self, data, query_parameters):
862 """Replaces matching substrings in a file.
863
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000864 If the 'replace_text' URL query parameter is present, it is expected to be
865 of the form old_text:new_text, which indicates that any old_text strings in
866 the file are replaced with new_text. Multiple 'replace_text' parameters may
867 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000868
869 If the parameters are not present, |data| is returned.
870 """
871 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000872 replace_text_values = query_dict.get('replace_text', [])
873 for replace_text_value in replace_text_values:
874 replace_text_args = replace_text_value.split(':')
875 if len(replace_text_args) != 2:
876 raise ValueError(
877 'replace_text must be of form old_text:new_text. Actual value: %s' %
878 replace_text_value)
879 old_text_b64, new_text_b64 = replace_text_args
880 old_text = base64.urlsafe_b64decode(old_text_b64)
881 new_text = base64.urlsafe_b64decode(new_text_b64)
882 data = data.replace(old_text, new_text)
883 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000884
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000885 def ZipFileHandler(self):
886 """This handler sends the contents of the requested file in compressed form.
887 Can pass in a parameter that specifies that the content length be
888 C - the compressed size (OK),
889 U - the uncompressed size (Non-standard, but handled),
890 S - less than compressed (OK because we keep going),
891 M - larger than compressed but less than uncompressed (an error),
892 L - larger than uncompressed (an error)
893 Example: compressedfiles/Picture_1.doc?C
894 """
895
896 prefix = "/compressedfiles/"
897 if not self.path.startswith(prefix):
898 return False
899
900 # Consume a request body if present.
901 if self.command == 'POST' or self.command == 'PUT' :
902 self.ReadRequestBody()
903
904 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
905
906 if not query in ('C', 'U', 'S', 'M', 'L'):
907 return False
908
909 sub_path = url_path[len(prefix):]
910 entries = sub_path.split('/')
911 file_path = os.path.join(self.server.data_dir, *entries)
912 if os.path.isdir(file_path):
913 file_path = os.path.join(file_path, 'index.html')
914
915 if not os.path.isfile(file_path):
916 print "File not found " + sub_path + " full path:" + file_path
917 self.send_error(404)
918 return True
919
920 f = open(file_path, "rb")
921 data = f.read()
922 uncompressed_len = len(data)
923 f.close()
924
925 # Compress the data.
926 data = zlib.compress(data)
927 compressed_len = len(data)
928
929 content_length = compressed_len
930 if query == 'U':
931 content_length = uncompressed_len
932 elif query == 'S':
933 content_length = compressed_len / 2
934 elif query == 'M':
935 content_length = (compressed_len + uncompressed_len) / 2
936 elif query == 'L':
937 content_length = compressed_len + uncompressed_len
938
939 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000940 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000941 self.send_header('Content-encoding', 'deflate')
942 self.send_header('Connection', 'close')
943 self.send_header('Content-Length', content_length)
944 self.send_header('ETag', '\'' + file_path + '\'')
945 self.end_headers()
946
947 self.wfile.write(data)
948
949 return True
950
initial.commit94958cf2008-07-26 22:42:52 +0000951 def FileHandler(self):
952 """This handler sends the contents of the requested file. Wow, it's like
953 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000954 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000955 if not self.path.startswith(prefix):
956 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000957 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000958
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000959 def PostOnlyFileHandler(self):
960 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000961 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000962 if not self.path.startswith(prefix):
963 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000964 return self._FileHandlerHelper(prefix)
965
966 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000967 request_body = ''
968 if self.command == 'POST' or self.command == 'PUT':
969 # Consume a request body if present.
970 request_body = self.ReadRequestBody()
971
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000972 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000973 query_dict = cgi.parse_qs(query)
974
975 expected_body = query_dict.get('expected_body', [])
976 if expected_body and request_body not in expected_body:
977 self.send_response(404)
978 self.end_headers()
979 self.wfile.write('')
980 return True
981
982 expected_headers = query_dict.get('expected_headers', [])
983 for expected_header in expected_headers:
984 header_name, expected_value = expected_header.split(':')
985 if self.headers.getheader(header_name) != expected_value:
986 self.send_response(404)
987 self.end_headers()
988 self.wfile.write('')
989 return True
990
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000991 sub_path = url_path[len(prefix):]
992 entries = sub_path.split('/')
993 file_path = os.path.join(self.server.data_dir, *entries)
994 if os.path.isdir(file_path):
995 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000996
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000997 if not os.path.isfile(file_path):
998 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000999 self.send_error(404)
1000 return True
1001
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001002 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001003 data = f.read()
1004 f.close()
1005
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001006 data = self._ReplaceFileData(data, query)
1007
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001008 old_protocol_version = self.protocol_version
1009
initial.commit94958cf2008-07-26 22:42:52 +00001010 # If file.mock-http-headers exists, it contains the headers we
1011 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001012 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001013 if os.path.isfile(headers_path):
1014 f = open(headers_path, "r")
1015
1016 # "HTTP/1.1 200 OK"
1017 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001018 http_major, http_minor, status_code = re.findall(
1019 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1020 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001021 self.send_response(int(status_code))
1022
1023 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001024 header_values = re.findall('(\S+):\s*(.*)', line)
1025 if len(header_values) > 0:
1026 # "name: value"
1027 name, value = header_values[0]
1028 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001029 f.close()
1030 else:
1031 # Could be more generic once we support mime-type sniffing, but for
1032 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001033
1034 range = self.headers.get('Range')
1035 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001036 # Note this doesn't handle all valid byte range values (i.e. left
1037 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001038 range = range[6:].split('-')
1039 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001040 if range[1]:
1041 end = int(range[1])
1042 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001043 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001044
1045 self.send_response(206)
1046 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1047 str(len(data))
1048 self.send_header('Content-Range', content_range)
1049 data = data[start: end + 1]
1050 else:
1051 self.send_response(200)
1052
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001053 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001054 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001055 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001056 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001057 self.end_headers()
1058
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001059 if (self.command != 'HEAD'):
1060 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001061
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001062 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001063 return True
1064
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001065 def SetCookieHandler(self):
1066 """This handler just sets a cookie, for testing cookie handling."""
1067
1068 if not self._ShouldHandleRequest("/set-cookie"):
1069 return False
1070
1071 query_char = self.path.find('?')
1072 if query_char != -1:
1073 cookie_values = self.path[query_char + 1:].split('&')
1074 else:
1075 cookie_values = ("",)
1076 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001077 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001078 for cookie_value in cookie_values:
1079 self.send_header('Set-Cookie', '%s' % cookie_value)
1080 self.end_headers()
1081 for cookie_value in cookie_values:
1082 self.wfile.write('%s' % cookie_value)
1083 return True
1084
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001085 def SetManyCookiesHandler(self):
1086 """This handler just sets a given number of cookies, for testing handling
1087 of large numbers of cookies."""
1088
1089 if not self._ShouldHandleRequest("/set-many-cookies"):
1090 return False
1091
1092 query_char = self.path.find('?')
1093 if query_char != -1:
1094 num_cookies = int(self.path[query_char + 1:])
1095 else:
1096 num_cookies = 0
1097 self.send_response(200)
1098 self.send_header('', 'text/html')
1099 for i in range(0, num_cookies):
1100 self.send_header('Set-Cookie', 'a=')
1101 self.end_headers()
1102 self.wfile.write('%d cookies were sent' % num_cookies)
1103 return True
1104
mattm@chromium.org983fc462012-06-30 00:52:08 +00001105 def ExpectAndSetCookieHandler(self):
1106 """Expects some cookies to be sent, and if they are, sets more cookies.
1107
1108 The expect parameter specifies a required cookie. May be specified multiple
1109 times.
1110 The set parameter specifies a cookie to set if all required cookies are
1111 preset. May be specified multiple times.
1112 The data parameter specifies the response body data to be returned."""
1113
1114 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1115 return False
1116
1117 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1118 query_dict = cgi.parse_qs(query)
1119 cookies = set()
1120 if 'Cookie' in self.headers:
1121 cookie_header = self.headers.getheader('Cookie')
1122 cookies.update([s.strip() for s in cookie_header.split(';')])
1123 got_all_expected_cookies = True
1124 for expected_cookie in query_dict.get('expect', []):
1125 if expected_cookie not in cookies:
1126 got_all_expected_cookies = False
1127 self.send_response(200)
1128 self.send_header('Content-Type', 'text/html')
1129 if got_all_expected_cookies:
1130 for cookie_value in query_dict.get('set', []):
1131 self.send_header('Set-Cookie', '%s' % cookie_value)
1132 self.end_headers()
1133 for data_value in query_dict.get('data', []):
1134 self.wfile.write(data_value)
1135 return True
1136
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001137 def SetHeaderHandler(self):
1138 """This handler sets a response header. Parameters are in the
1139 key%3A%20value&key2%3A%20value2 format."""
1140
1141 if not self._ShouldHandleRequest("/set-header"):
1142 return False
1143
1144 query_char = self.path.find('?')
1145 if query_char != -1:
1146 headers_values = self.path[query_char + 1:].split('&')
1147 else:
1148 headers_values = ("",)
1149 self.send_response(200)
1150 self.send_header('Content-Type', 'text/html')
1151 for header_value in headers_values:
1152 header_value = urllib.unquote(header_value)
1153 (key, value) = header_value.split(': ', 1)
1154 self.send_header(key, value)
1155 self.end_headers()
1156 for header_value in headers_values:
1157 self.wfile.write('%s' % header_value)
1158 return True
1159
initial.commit94958cf2008-07-26 22:42:52 +00001160 def AuthBasicHandler(self):
1161 """This handler tests 'Basic' authentication. It just sends a page with
1162 title 'user/pass' if you succeed."""
1163
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001164 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001165 return False
1166
1167 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001168 expected_password = 'secret'
1169 realm = 'testrealm'
1170 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001171
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001172 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1173 query_params = cgi.parse_qs(query, True)
1174 if 'set-cookie-if-challenged' in query_params:
1175 set_cookie_if_challenged = True
1176 if 'password' in query_params:
1177 expected_password = query_params['password'][0]
1178 if 'realm' in query_params:
1179 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001180
initial.commit94958cf2008-07-26 22:42:52 +00001181 auth = self.headers.getheader('authorization')
1182 try:
1183 if not auth:
1184 raise Exception('no auth')
1185 b64str = re.findall(r'Basic (\S+)', auth)[0]
1186 userpass = base64.b64decode(b64str)
1187 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001188 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001189 raise Exception('wrong password')
1190 except Exception, e:
1191 # Authentication failed.
1192 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001193 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001194 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001195 if set_cookie_if_challenged:
1196 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001197 self.end_headers()
1198 self.wfile.write('<html><head>')
1199 self.wfile.write('<title>Denied: %s</title>' % e)
1200 self.wfile.write('</head><body>')
1201 self.wfile.write('auth=%s<p>' % auth)
1202 self.wfile.write('b64str=%s<p>' % b64str)
1203 self.wfile.write('username: %s<p>' % username)
1204 self.wfile.write('userpass: %s<p>' % userpass)
1205 self.wfile.write('password: %s<p>' % password)
1206 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1207 self.wfile.write('</body></html>')
1208 return True
1209
1210 # Authentication successful. (Return a cachable response to allow for
1211 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001212 old_protocol_version = self.protocol_version
1213 self.protocol_version = "HTTP/1.1"
1214
initial.commit94958cf2008-07-26 22:42:52 +00001215 if_none_match = self.headers.getheader('if-none-match')
1216 if if_none_match == "abc":
1217 self.send_response(304)
1218 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001219 elif url_path.endswith(".gif"):
1220 # Using chrome/test/data/google/logo.gif as the test image
1221 test_image_path = ['google', 'logo.gif']
1222 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1223 if not os.path.isfile(gif_path):
1224 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001225 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001226 return True
1227
1228 f = open(gif_path, "rb")
1229 data = f.read()
1230 f.close()
1231
1232 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001233 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001234 self.send_header('Cache-control', 'max-age=60000')
1235 self.send_header('Etag', 'abc')
1236 self.end_headers()
1237 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001238 else:
1239 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001240 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001241 self.send_header('Cache-control', 'max-age=60000')
1242 self.send_header('Etag', 'abc')
1243 self.end_headers()
1244 self.wfile.write('<html><head>')
1245 self.wfile.write('<title>%s/%s</title>' % (username, password))
1246 self.wfile.write('</head><body>')
1247 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001248 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001249 self.wfile.write('</body></html>')
1250
rvargas@google.com54453b72011-05-19 01:11:11 +00001251 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001252 return True
1253
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001254 def GDataAuthHandler(self):
1255 """This handler verifies the Authentication header for GData requests."""
1256 if not self.server.gdata_auth_token:
1257 # --auth-token is not specified, not the test case for GData.
1258 return False
1259
1260 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1261 return False
1262
1263 if 'GData-Version' not in self.headers:
1264 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1265 return True
1266
1267 if 'Authorization' not in self.headers:
1268 self.send_error(httplib.UNAUTHORIZED)
1269 return True
1270
1271 field_prefix = 'Bearer '
1272 authorization = self.headers['Authorization']
1273 if not authorization.startswith(field_prefix):
1274 self.send_error(httplib.UNAUTHORIZED)
1275 return True
1276
1277 code = authorization[len(field_prefix):]
1278 if code != self.server.gdata_auth_token:
1279 self.send_error(httplib.UNAUTHORIZED)
1280 return True
1281
1282 return False
1283
1284 def GDataDocumentsFeedQueryHandler(self):
1285 """This handler verifies if required parameters are properly
1286 specified for the GData DocumentsFeed request."""
1287 if not self.server.gdata_auth_token:
1288 # --auth-token is not specified, not the test case for GData.
1289 return False
1290
1291 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1292 return False
1293
1294 (path, question, query_params) = self.path.partition('?')
1295 self.query_params = urlparse.parse_qs(query_params)
1296
1297 if 'v' not in self.query_params:
1298 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1299 return True
1300 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1301 # currently our GData client only uses JSON format.
1302 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1303 return True
1304
1305 return False
1306
tonyg@chromium.org75054202010-03-31 22:06:10 +00001307 def GetNonce(self, force_reset=False):
1308 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001309
tonyg@chromium.org75054202010-03-31 22:06:10 +00001310 This is a fake implementation. A real implementation would only use a given
1311 nonce a single time (hence the name n-once). However, for the purposes of
1312 unittesting, we don't care about the security of the nonce.
1313
1314 Args:
1315 force_reset: Iff set, the nonce will be changed. Useful for testing the
1316 "stale" response.
1317 """
1318 if force_reset or not self.server.nonce_time:
1319 self.server.nonce_time = time.time()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001320 return _new_md5('privatekey%s%d' %
1321 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001322
1323 def AuthDigestHandler(self):
1324 """This handler tests 'Digest' authentication.
1325
1326 It just sends a page with title 'user/pass' if you succeed.
1327
1328 A stale response is sent iff "stale" is present in the request path.
1329 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001330 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001331 return False
1332
tonyg@chromium.org75054202010-03-31 22:06:10 +00001333 stale = 'stale' in self.path
1334 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001335 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001336 password = 'secret'
1337 realm = 'testrealm'
1338
1339 auth = self.headers.getheader('authorization')
1340 pairs = {}
1341 try:
1342 if not auth:
1343 raise Exception('no auth')
1344 if not auth.startswith('Digest'):
1345 raise Exception('not digest')
1346 # Pull out all the name="value" pairs as a dictionary.
1347 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1348
1349 # Make sure it's all valid.
1350 if pairs['nonce'] != nonce:
1351 raise Exception('wrong nonce')
1352 if pairs['opaque'] != opaque:
1353 raise Exception('wrong opaque')
1354
1355 # Check the 'response' value and make sure it matches our magic hash.
1356 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001357 hash_a1 = _new_md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001358 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001359 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001360 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001361 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001362 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1363 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001364 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001365
1366 if pairs['response'] != response:
1367 raise Exception('wrong password')
1368 except Exception, e:
1369 # Authentication failed.
1370 self.send_response(401)
1371 hdr = ('Digest '
1372 'realm="%s", '
1373 'domain="/", '
1374 'qop="auth", '
1375 'algorithm=MD5, '
1376 'nonce="%s", '
1377 'opaque="%s"') % (realm, nonce, opaque)
1378 if stale:
1379 hdr += ', stale="TRUE"'
1380 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001381 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001382 self.end_headers()
1383 self.wfile.write('<html><head>')
1384 self.wfile.write('<title>Denied: %s</title>' % e)
1385 self.wfile.write('</head><body>')
1386 self.wfile.write('auth=%s<p>' % auth)
1387 self.wfile.write('pairs=%s<p>' % pairs)
1388 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1389 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1390 self.wfile.write('</body></html>')
1391 return True
1392
1393 # Authentication successful.
1394 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001395 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001396 self.end_headers()
1397 self.wfile.write('<html><head>')
1398 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1399 self.wfile.write('</head><body>')
1400 self.wfile.write('auth=%s<p>' % auth)
1401 self.wfile.write('pairs=%s<p>' % pairs)
1402 self.wfile.write('</body></html>')
1403
1404 return True
1405
1406 def SlowServerHandler(self):
1407 """Wait for the user suggested time before responding. The syntax is
1408 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001409 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001410 return False
1411 query_char = self.path.find('?')
1412 wait_sec = 1.0
1413 if query_char >= 0:
1414 try:
1415 wait_sec = int(self.path[query_char + 1:])
1416 except ValueError:
1417 pass
1418 time.sleep(wait_sec)
1419 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001420 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001421 self.end_headers()
1422 self.wfile.write("waited %d seconds" % wait_sec)
1423 return True
1424
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001425 def ChunkedServerHandler(self):
1426 """Send chunked response. Allows to specify chunks parameters:
1427 - waitBeforeHeaders - ms to wait before sending headers
1428 - waitBetweenChunks - ms to wait between chunks
1429 - chunkSize - size of each chunk in bytes
1430 - chunksNumber - number of chunks
1431 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1432 waits one second, then sends headers and five chunks five bytes each."""
1433 if not self._ShouldHandleRequest("/chunked"):
1434 return False
1435 query_char = self.path.find('?')
1436 chunkedSettings = {'waitBeforeHeaders' : 0,
1437 'waitBetweenChunks' : 0,
1438 'chunkSize' : 5,
1439 'chunksNumber' : 5}
1440 if query_char >= 0:
1441 params = self.path[query_char + 1:].split('&')
1442 for param in params:
1443 keyValue = param.split('=')
1444 if len(keyValue) == 2:
1445 try:
1446 chunkedSettings[keyValue[0]] = int(keyValue[1])
1447 except ValueError:
1448 pass
1449 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1450 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1451 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001452 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001453 self.send_header('Connection', 'close')
1454 self.send_header('Transfer-Encoding', 'chunked')
1455 self.end_headers()
1456 # Chunked encoding: sending all chunks, then final zero-length chunk and
1457 # then final CRLF.
1458 for i in range(0, chunkedSettings['chunksNumber']):
1459 if i > 0:
1460 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1461 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1462 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1463 self.sendChunkHelp('')
1464 return True
1465
initial.commit94958cf2008-07-26 22:42:52 +00001466 def ContentTypeHandler(self):
1467 """Returns a string of html with the given content type. E.g.,
1468 /contenttype?text/css returns an html file with the Content-Type
1469 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001470 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001471 return False
1472 query_char = self.path.find('?')
1473 content_type = self.path[query_char + 1:].strip()
1474 if not content_type:
1475 content_type = 'text/html'
1476 self.send_response(200)
1477 self.send_header('Content-Type', content_type)
1478 self.end_headers()
1479 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1480 return True
1481
creis@google.com2f4f6a42011-03-25 19:44:19 +00001482 def NoContentHandler(self):
1483 """Returns a 204 No Content response."""
1484 if not self._ShouldHandleRequest("/nocontent"):
1485 return False
1486 self.send_response(204)
1487 self.end_headers()
1488 return True
1489
initial.commit94958cf2008-07-26 22:42:52 +00001490 def ServerRedirectHandler(self):
1491 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001492 '/server-redirect?http://foo.bar/asdf' to redirect to
1493 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001494
1495 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001496 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001497 return False
1498
1499 query_char = self.path.find('?')
1500 if query_char < 0 or len(self.path) <= query_char + 1:
1501 self.sendRedirectHelp(test_name)
1502 return True
1503 dest = self.path[query_char + 1:]
1504
1505 self.send_response(301) # moved permanently
1506 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001507 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001508 self.end_headers()
1509 self.wfile.write('<html><head>')
1510 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1511
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001512 return True
initial.commit94958cf2008-07-26 22:42:52 +00001513
1514 def ClientRedirectHandler(self):
1515 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001516 '/client-redirect?http://foo.bar/asdf' to redirect to
1517 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001518
1519 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001520 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001521 return False
1522
1523 query_char = self.path.find('?');
1524 if query_char < 0 or len(self.path) <= query_char + 1:
1525 self.sendRedirectHelp(test_name)
1526 return True
1527 dest = self.path[query_char + 1:]
1528
1529 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001530 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001531 self.end_headers()
1532 self.wfile.write('<html><head>')
1533 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1534 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1535
1536 return True
1537
tony@chromium.org03266982010-03-05 03:18:42 +00001538 def MultipartHandler(self):
1539 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001540 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001541 if not self._ShouldHandleRequest(test_name):
1542 return False
1543
1544 num_frames = 10
1545 bound = '12345'
1546 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001547 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001548 'multipart/x-mixed-replace;boundary=' + bound)
1549 self.end_headers()
1550
1551 for i in xrange(num_frames):
1552 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001553 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001554 self.wfile.write('<title>page ' + str(i) + '</title>')
1555 self.wfile.write('page ' + str(i))
1556
1557 self.wfile.write('--' + bound + '--')
1558 return True
1559
tony@chromium.org4cb88302011-09-27 22:13:49 +00001560 def MultipartSlowHandler(self):
1561 """Send a multipart response (3 text/html pages) with a slight delay
1562 between each page. This is similar to how some pages show status using
1563 multipart."""
1564 test_name = '/multipart-slow'
1565 if not self._ShouldHandleRequest(test_name):
1566 return False
1567
1568 num_frames = 3
1569 bound = '12345'
1570 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001571 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001572 'multipart/x-mixed-replace;boundary=' + bound)
1573 self.end_headers()
1574
1575 for i in xrange(num_frames):
1576 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001577 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001578 time.sleep(0.25)
1579 if i == 2:
1580 self.wfile.write('<title>PASS</title>')
1581 else:
1582 self.wfile.write('<title>page ' + str(i) + '</title>')
1583 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1584
1585 self.wfile.write('--' + bound + '--')
1586 return True
1587
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001588 def GetSSLSessionCacheHandler(self):
1589 """Send a reply containing a log of the session cache operations."""
1590
1591 if not self._ShouldHandleRequest('/ssl-session-cache'):
1592 return False
1593
1594 self.send_response(200)
1595 self.send_header('Content-Type', 'text/plain')
1596 self.end_headers()
1597 try:
1598 for (action, sessionID) in self.server.session_cache.log:
1599 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1600 except AttributeError, e:
1601 self.wfile.write('Pass --https-record-resume in order to use' +
1602 ' this request')
1603 return True
1604
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001605 def CloseSocketHandler(self):
1606 """Closes the socket without sending anything."""
1607
1608 if not self._ShouldHandleRequest('/close-socket'):
1609 return False
1610
1611 self.wfile.close()
1612 return True
1613
initial.commit94958cf2008-07-26 22:42:52 +00001614 def DefaultResponseHandler(self):
1615 """This is the catch-all response handler for requests that aren't handled
1616 by one of the special handlers above.
1617 Note that we specify the content-length as without it the https connection
1618 is not closed properly (and the browser keeps expecting data)."""
1619
1620 contents = "Default response given for path: " + self.path
1621 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001622 self.send_header('Content-Type', 'text/html')
1623 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001624 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001625 if (self.command != 'HEAD'):
1626 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001627 return True
1628
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001629 def RedirectConnectHandler(self):
1630 """Sends a redirect to the CONNECT request for www.redirect.com. This
1631 response is not specified by the RFC, so the browser should not follow
1632 the redirect."""
1633
1634 if (self.path.find("www.redirect.com") < 0):
1635 return False
1636
1637 dest = "http://www.destination.com/foo.js"
1638
1639 self.send_response(302) # moved temporarily
1640 self.send_header('Location', dest)
1641 self.send_header('Connection', 'close')
1642 self.end_headers()
1643 return True
1644
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001645 def ServerAuthConnectHandler(self):
1646 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1647 response doesn't make sense because the proxy server cannot request
1648 server authentication."""
1649
1650 if (self.path.find("www.server-auth.com") < 0):
1651 return False
1652
1653 challenge = 'Basic realm="WallyWorld"'
1654
1655 self.send_response(401) # unauthorized
1656 self.send_header('WWW-Authenticate', challenge)
1657 self.send_header('Connection', 'close')
1658 self.end_headers()
1659 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001660
1661 def DefaultConnectResponseHandler(self):
1662 """This is the catch-all response handler for CONNECT requests that aren't
1663 handled by one of the special handlers above. Real Web servers respond
1664 with 400 to CONNECT requests."""
1665
1666 contents = "Your client has issued a malformed or illegal request."
1667 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001668 self.send_header('Content-Type', 'text/html')
1669 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001670 self.end_headers()
1671 self.wfile.write(contents)
1672 return True
1673
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001674 def DeviceManagementHandler(self):
1675 """Delegates to the device management service used for cloud policy."""
1676 if not self._ShouldHandleRequest("/device_management"):
1677 return False
1678
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001679 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001680
1681 if not self.server._device_management_handler:
1682 import device_management
1683 policy_path = os.path.join(self.server.data_dir, 'device_management')
1684 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001685 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001686 self.server.policy_keys,
1687 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001688
1689 http_response, raw_reply = (
1690 self.server._device_management_handler.HandleRequest(self.path,
1691 self.headers,
1692 raw_request))
1693 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001694 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001695 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001696 self.end_headers()
1697 self.wfile.write(raw_reply)
1698 return True
1699
initial.commit94958cf2008-07-26 22:42:52 +00001700 # called by the redirect handling function when there is no parameter
1701 def sendRedirectHelp(self, redirect_name):
1702 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001703 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001704 self.end_headers()
1705 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1706 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1707 self.wfile.write('</body></html>')
1708
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001709 # called by chunked handling function
1710 def sendChunkHelp(self, chunk):
1711 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1712 self.wfile.write('%X\r\n' % len(chunk))
1713 self.wfile.write(chunk)
1714 self.wfile.write('\r\n')
1715
akalin@chromium.org154bb132010-11-12 02:20:27 +00001716
1717class SyncPageHandler(BasePageHandler):
1718 """Handler for the main HTTP sync server."""
1719
1720 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001721 get_handlers = [self.ChromiumSyncTimeHandler,
1722 self.ChromiumSyncMigrationOpHandler,
1723 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001724 self.ChromiumSyncDisableNotificationsOpHandler,
1725 self.ChromiumSyncEnableNotificationsOpHandler,
1726 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001727 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001728 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001729 self.ChromiumSyncErrorOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001730 self.ChromiumSyncSyncTabsOpHandler,
1731 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001732
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001733 post_handlers = [self.ChromiumSyncCommandHandler,
1734 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001735 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001736 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001737 post_handlers, [])
1738
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001739
akalin@chromium.org154bb132010-11-12 02:20:27 +00001740 def ChromiumSyncTimeHandler(self):
1741 """Handle Chromium sync .../time requests.
1742
1743 The syncer sometimes checks server reachability by examining /time.
1744 """
1745 test_name = "/chromiumsync/time"
1746 if not self._ShouldHandleRequest(test_name):
1747 return False
1748
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001749 # Chrome hates it if we send a response before reading the request.
1750 if self.headers.getheader('content-length'):
1751 length = int(self.headers.getheader('content-length'))
1752 raw_request = self.rfile.read(length)
1753
akalin@chromium.org154bb132010-11-12 02:20:27 +00001754 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001755 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001756 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001757 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001758 return True
1759
1760 def ChromiumSyncCommandHandler(self):
1761 """Handle a chromiumsync command arriving via http.
1762
1763 This covers all sync protocol commands: authentication, getupdates, and
1764 commit.
1765 """
1766 test_name = "/chromiumsync/command"
1767 if not self._ShouldHandleRequest(test_name):
1768 return False
1769
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001770 length = int(self.headers.getheader('content-length'))
1771 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001772 http_response = 200
1773 raw_reply = None
1774 if not self.server.GetAuthenticated():
1775 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001776 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1777 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001778 else:
1779 http_response, raw_reply = self.server.HandleCommand(
1780 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001781
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001782 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001783 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001784 if http_response == 401:
1785 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001786 self.end_headers()
1787 self.wfile.write(raw_reply)
1788 return True
1789
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001790 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001791 test_name = "/chromiumsync/migrate"
1792 if not self._ShouldHandleRequest(test_name):
1793 return False
1794
1795 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1796 self.path)
1797 self.send_response(http_response)
1798 self.send_header('Content-Type', 'text/html')
1799 self.send_header('Content-Length', len(raw_reply))
1800 self.end_headers()
1801 self.wfile.write(raw_reply)
1802 return True
1803
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001804 def ChromiumSyncCredHandler(self):
1805 test_name = "/chromiumsync/cred"
1806 if not self._ShouldHandleRequest(test_name):
1807 return False
1808 try:
1809 query = urlparse.urlparse(self.path)[4]
1810 cred_valid = urlparse.parse_qs(query)['valid']
1811 if cred_valid[0] == 'True':
1812 self.server.SetAuthenticated(True)
1813 else:
1814 self.server.SetAuthenticated(False)
1815 except:
1816 self.server.SetAuthenticated(False)
1817
1818 http_response = 200
1819 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1820 self.send_response(http_response)
1821 self.send_header('Content-Type', 'text/html')
1822 self.send_header('Content-Length', len(raw_reply))
1823 self.end_headers()
1824 self.wfile.write(raw_reply)
1825 return True
1826
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001827 def ChromiumSyncDisableNotificationsOpHandler(self):
1828 test_name = "/chromiumsync/disablenotifications"
1829 if not self._ShouldHandleRequest(test_name):
1830 return False
1831 self.server.GetXmppServer().DisableNotifications()
1832 result = 200
1833 raw_reply = ('<html><title>Notifications disabled</title>'
1834 '<H1>Notifications disabled</H1></html>')
1835 self.send_response(result)
1836 self.send_header('Content-Type', 'text/html')
1837 self.send_header('Content-Length', len(raw_reply))
1838 self.end_headers()
1839 self.wfile.write(raw_reply)
1840 return True;
1841
1842 def ChromiumSyncEnableNotificationsOpHandler(self):
1843 test_name = "/chromiumsync/enablenotifications"
1844 if not self._ShouldHandleRequest(test_name):
1845 return False
1846 self.server.GetXmppServer().EnableNotifications()
1847 result = 200
1848 raw_reply = ('<html><title>Notifications enabled</title>'
1849 '<H1>Notifications enabled</H1></html>')
1850 self.send_response(result)
1851 self.send_header('Content-Type', 'text/html')
1852 self.send_header('Content-Length', len(raw_reply))
1853 self.end_headers()
1854 self.wfile.write(raw_reply)
1855 return True;
1856
1857 def ChromiumSyncSendNotificationOpHandler(self):
1858 test_name = "/chromiumsync/sendnotification"
1859 if not self._ShouldHandleRequest(test_name):
1860 return False
1861 query = urlparse.urlparse(self.path)[4]
1862 query_params = urlparse.parse_qs(query)
1863 channel = ''
1864 data = ''
1865 if 'channel' in query_params:
1866 channel = query_params['channel'][0]
1867 if 'data' in query_params:
1868 data = query_params['data'][0]
1869 self.server.GetXmppServer().SendNotification(channel, data)
1870 result = 200
1871 raw_reply = ('<html><title>Notification sent</title>'
1872 '<H1>Notification sent with channel "%s" '
1873 'and data "%s"</H1></html>'
1874 % (channel, data))
1875 self.send_response(result)
1876 self.send_header('Content-Type', 'text/html')
1877 self.send_header('Content-Length', len(raw_reply))
1878 self.end_headers()
1879 self.wfile.write(raw_reply)
1880 return True;
1881
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001882 def ChromiumSyncBirthdayErrorOpHandler(self):
1883 test_name = "/chromiumsync/birthdayerror"
1884 if not self._ShouldHandleRequest(test_name):
1885 return False
1886 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1887 self.send_response(result)
1888 self.send_header('Content-Type', 'text/html')
1889 self.send_header('Content-Length', len(raw_reply))
1890 self.end_headers()
1891 self.wfile.write(raw_reply)
1892 return True;
1893
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001894 def ChromiumSyncTransientErrorOpHandler(self):
1895 test_name = "/chromiumsync/transienterror"
1896 if not self._ShouldHandleRequest(test_name):
1897 return False
1898 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1899 self.send_response(result)
1900 self.send_header('Content-Type', 'text/html')
1901 self.send_header('Content-Length', len(raw_reply))
1902 self.end_headers()
1903 self.wfile.write(raw_reply)
1904 return True;
1905
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001906 def ChromiumSyncErrorOpHandler(self):
1907 test_name = "/chromiumsync/error"
1908 if not self._ShouldHandleRequest(test_name):
1909 return False
1910 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1911 self.path)
1912 self.send_response(result)
1913 self.send_header('Content-Type', 'text/html')
1914 self.send_header('Content-Length', len(raw_reply))
1915 self.end_headers()
1916 self.wfile.write(raw_reply)
1917 return True;
1918
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001919 def ChromiumSyncSyncTabsOpHandler(self):
1920 test_name = "/chromiumsync/synctabs"
1921 if not self._ShouldHandleRequest(test_name):
1922 return False
1923 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1924 self.send_response(result)
1925 self.send_header('Content-Type', 'text/html')
1926 self.send_header('Content-Length', len(raw_reply))
1927 self.end_headers()
1928 self.wfile.write(raw_reply)
1929 return True;
1930
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001931 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1932 test_name = "/chromiumsync/createsyncedbookmarks"
1933 if not self._ShouldHandleRequest(test_name):
1934 return False
1935 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1936 self.send_response(result)
1937 self.send_header('Content-Type', 'text/html')
1938 self.send_header('Content-Length', len(raw_reply))
1939 self.end_headers()
1940 self.wfile.write(raw_reply)
1941 return True;
1942
akalin@chromium.org154bb132010-11-12 02:20:27 +00001943
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001944def MakeDataDir():
1945 if options.data_dir:
1946 if not os.path.isdir(options.data_dir):
1947 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1948 return None
1949 my_data_dir = options.data_dir
1950 else:
1951 # Create the default path to our data dir, relative to the exe dir.
1952 my_data_dir = os.path.dirname(sys.argv[0])
1953 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1954 "test", "data")
1955
1956 #TODO(ibrar): Must use Find* funtion defined in google\tools
1957 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1958
1959 return my_data_dir
1960
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001961class OCSPHandler(BasePageHandler):
1962 def __init__(self, request, client_address, socket_server):
1963 handlers = [self.OCSPResponse]
1964 self.ocsp_response = socket_server.ocsp_response
1965 BasePageHandler.__init__(self, request, client_address, socket_server,
1966 [], handlers, [], handlers, [])
1967
1968 def OCSPResponse(self):
1969 self.send_response(200)
1970 self.send_header('Content-Type', 'application/ocsp-response')
1971 self.send_header('Content-Length', str(len(self.ocsp_response)))
1972 self.end_headers()
1973
1974 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001975
1976class TCPEchoHandler(SocketServer.BaseRequestHandler):
1977 """The RequestHandler class for TCP echo server.
1978
1979 It is instantiated once per connection to the server, and overrides the
1980 handle() method to implement communication to the client.
1981 """
1982
1983 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001984 """Handles the request from the client and constructs a response."""
1985
1986 data = self.request.recv(65536).strip()
1987 # Verify the "echo request" message received from the client. Send back
1988 # "echo response" message if "echo request" message is valid.
1989 try:
1990 return_data = echo_message.GetEchoResponseData(data)
1991 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001992 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001993 except ValueError:
1994 return
1995
1996 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001997
1998
1999class UDPEchoHandler(SocketServer.BaseRequestHandler):
2000 """The RequestHandler class for UDP echo server.
2001
2002 It is instantiated once per connection to the server, and overrides the
2003 handle() method to implement communication to the client.
2004 """
2005
2006 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002007 """Handles the request from the client and constructs a response."""
2008
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002009 data = self.request[0].strip()
2010 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002011 # Verify the "echo request" message received from the client. Send back
2012 # "echo response" message if "echo request" message is valid.
2013 try:
2014 return_data = echo_message.GetEchoResponseData(data)
2015 if not return_data:
2016 return
2017 except ValueError:
2018 return
2019 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002020
2021
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002022class FileMultiplexer:
2023 def __init__(self, fd1, fd2) :
2024 self.__fd1 = fd1
2025 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002026
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002027 def __del__(self) :
2028 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2029 self.__fd1.close()
2030 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2031 self.__fd2.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002032
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002033 def write(self, text) :
2034 self.__fd1.write(text)
2035 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002036
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002037 def flush(self) :
2038 self.__fd1.flush()
2039 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002040
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002041def main(options, args):
2042 logfile = open('testserver.log', 'w')
2043 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2044 if options.log_to_console:
2045 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2046 else:
2047 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002048
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002049 port = options.port
2050 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002051
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002052 server_data = {}
2053 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002054
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002055 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002056
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002057 if options.server_type == SERVER_HTTP:
2058 if options.https:
2059 pem_cert_and_key = None
2060 if options.cert_and_key_file:
2061 if not os.path.isfile(options.cert_and_key_file):
2062 print ('specified server cert file not found: ' +
2063 options.cert_and_key_file + ' exiting...')
2064 return
2065 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002066 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002067 # generate a new certificate and run an OCSP server for it.
2068 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2069 print ('OCSP server started on %s:%d...' %
2070 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002071
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002072 ocsp_der = None
2073 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002074
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002075 if options.ocsp == 'ok':
2076 ocsp_state = minica.OCSP_STATE_GOOD
2077 elif options.ocsp == 'revoked':
2078 ocsp_state = minica.OCSP_STATE_REVOKED
2079 elif options.ocsp == 'invalid':
2080 ocsp_state = minica.OCSP_STATE_INVALID
2081 elif options.ocsp == 'unauthorized':
2082 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2083 elif options.ocsp == 'unknown':
2084 ocsp_state = minica.OCSP_STATE_UNKNOWN
2085 else:
2086 print 'unknown OCSP status: ' + options.ocsp_status
2087 return
mattm@chromium.org07e28412012-09-05 00:19:41 +00002088
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002089 (pem_cert_and_key, ocsp_der) = \
2090 minica.GenerateCertKeyAndOCSP(
2091 subject = "127.0.0.1",
2092 ocsp_url = ("http://%s:%d/ocsp" %
2093 (host, ocsp_server.server_port)),
2094 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002095
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002096 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002097
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002098 for ca_cert in options.ssl_client_ca:
2099 if not os.path.isfile(ca_cert):
2100 print 'specified trusted client CA file not found: ' + ca_cert + \
2101 ' exiting...'
2102 return
2103 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2104 options.ssl_client_auth, options.ssl_client_ca,
2105 options.ssl_bulk_cipher, options.record_resume,
2106 options.tls_intolerant)
2107 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002108 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002109 server = HTTPServer((host, port), TestPageHandler)
2110 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002111
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002112 server.data_dir = MakeDataDir()
2113 server.file_root_url = options.file_root_url
2114 server_data['port'] = server.server_port
2115 server._device_management_handler = None
2116 server.policy_keys = options.policy_keys
2117 server.policy_user = options.policy_user
2118 server.gdata_auth_token = options.auth_token
2119 elif options.server_type == SERVER_SYNC:
2120 xmpp_port = options.xmpp_port
2121 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2122 print 'Sync HTTP server started on port %d...' % server.server_port
2123 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2124 server_data['port'] = server.server_port
2125 server_data['xmpp_port'] = server.xmpp_port
2126 elif options.server_type == SERVER_TCP_ECHO:
2127 # Used for generating the key (randomly) that encodes the "echo request"
2128 # message.
2129 random.seed()
2130 server = TCPEchoServer((host, port), TCPEchoHandler)
2131 print 'Echo TCP server started on port %d...' % server.server_port
2132 server_data['port'] = server.server_port
2133 elif options.server_type == SERVER_UDP_ECHO:
2134 # Used for generating the key (randomly) that encodes the "echo request"
2135 # message.
2136 random.seed()
2137 server = UDPEchoServer((host, port), UDPEchoHandler)
2138 print 'Echo UDP server started on port %d...' % server.server_port
2139 server_data['port'] = server.server_port
2140 # means FTP Server
2141 else:
2142 my_data_dir = MakeDataDir()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002143
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002144 # Instantiate a dummy authorizer for managing 'virtual' users
2145 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002146
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002147 # Define a new user having full r/w permissions and a read-only
2148 # anonymous user
2149 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002150
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002151 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002152
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002153 # Instantiate FTP handler class
2154 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2155 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002156
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002157 # Define a customized banner (string returned when client connects)
2158 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2159 pyftpdlib.ftpserver.__ver__)
2160
2161 # Instantiate FTP server class and listen to address:port
2162 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2163 server_data['port'] = server.socket.getsockname()[1]
2164 print 'FTP server started on port %d...' % server_data['port']
2165
2166 # Notify the parent that we've started. (BaseServer subclasses
2167 # bind their sockets on construction.)
2168 if options.startup_pipe is not None:
2169 server_data_json = json.dumps(server_data)
2170 server_data_len = len(server_data_json)
2171 print 'sending server_data: %s (%d bytes)' % (
2172 server_data_json, server_data_len)
2173 if sys.platform == 'win32':
2174 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2175 else:
2176 fd = options.startup_pipe
2177 startup_pipe = os.fdopen(fd, "w")
2178 # First write the data length as an unsigned 4-byte value. This
2179 # is _not_ using network byte ordering since the other end of the
2180 # pipe is on the same machine.
2181 startup_pipe.write(struct.pack('=L', server_data_len))
2182 startup_pipe.write(server_data_json)
2183 startup_pipe.close()
2184
2185 if ocsp_server is not None:
2186 ocsp_server.serve_forever_on_thread()
2187
2188 try:
2189 server.serve_forever()
2190 except KeyboardInterrupt:
2191 print 'shutting down server'
2192 if ocsp_server is not None:
2193 ocsp_server.stop_serving()
2194 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002195
2196if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002197 option_parser = optparse.OptionParser()
2198 option_parser.add_option("-f", '--ftp', action='store_const',
2199 const=SERVER_FTP, default=SERVER_HTTP,
2200 dest='server_type',
2201 help='start up an FTP server.')
2202 option_parser.add_option('', '--sync', action='store_const',
2203 const=SERVER_SYNC, default=SERVER_HTTP,
2204 dest='server_type',
2205 help='start up a sync server.')
2206 option_parser.add_option('', '--tcp-echo', action='store_const',
2207 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2208 dest='server_type',
2209 help='start up a tcp echo server.')
2210 option_parser.add_option('', '--udp-echo', action='store_const',
2211 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2212 dest='server_type',
2213 help='start up a udp echo server.')
2214 option_parser.add_option('', '--log-to-console', action='store_const',
2215 const=True, default=False,
2216 dest='log_to_console',
2217 help='Enables or disables sys.stdout logging to '
2218 'the console.')
2219 option_parser.add_option('', '--port', default='0', type='int',
2220 help='Port used by the server. If unspecified, the '
2221 'server will listen on an ephemeral port.')
2222 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2223 help='Port used by the XMPP server. If unspecified, '
2224 'the XMPP server will listen on an ephemeral port.')
2225 option_parser.add_option('', '--data-dir', dest='data_dir',
2226 help='Directory from which to read the files.')
2227 option_parser.add_option('', '--https', action='store_true', dest='https',
2228 help='Specify that https should be used.')
2229 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2230 help='specify the path to the file containing the '
2231 'certificate and private key for the server in PEM '
2232 'format')
2233 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2234 help='The type of OCSP response generated for the '
2235 'automatically generated certificate. One of '
2236 '[ok,revoked,invalid]')
2237 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2238 default='0', type='int',
2239 help='If nonzero, certain TLS connections will be'
2240 ' aborted in order to test version fallback. 1'
2241 ' means all TLS versions will be aborted. 2 means'
2242 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2243 ' 1.2 or higher will be aborted.')
2244 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2245 const=True, default=False, action='store_const',
2246 help='Record resumption cache events rather than'
2247 ' resuming as normal. Allows the use of the'
2248 ' /ssl-session-cache request')
2249 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2250 help='Require SSL client auth on every connection.')
2251 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2252 help='Specify that the client certificate request '
2253 'should include the CA named in the subject of '
2254 'the DER-encoded certificate contained in the '
2255 'specified file. This option may appear multiple '
2256 'times, indicating multiple CA names should be '
2257 'sent in the request.')
2258 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2259 help='Specify the bulk encryption algorithm(s)'
2260 'that will be accepted by the SSL server. Valid '
2261 'values are "aes256", "aes128", "3des", "rc4". If '
2262 'omitted, all algorithms will be used. This '
2263 'option may appear multiple times, indicating '
2264 'multiple algorithms should be enabled.');
2265 option_parser.add_option('', '--file-root-url', default='/files/',
2266 help='Specify a root URL for files served.')
2267 option_parser.add_option('', '--startup-pipe', type='int',
2268 dest='startup_pipe',
2269 help='File handle of pipe to parent process')
2270 option_parser.add_option('', '--policy-key', action='append',
2271 dest='policy_keys',
2272 help='Specify a path to a PEM-encoded private key '
2273 'to use for policy signing. May be specified '
2274 'multiple times in order to load multipe keys into '
2275 'the server. If ther server has multiple keys, it '
2276 'will rotate through them in at each request a '
2277 'round-robin fashion. The server will generate a '
2278 'random key if none is specified on the command '
2279 'line.')
2280 option_parser.add_option('', '--policy-user', default='user@example.com',
2281 dest='policy_user',
2282 help='Specify the user name the server should '
2283 'report back to the client as the user owning the '
2284 'token used for making the policy request.')
2285 option_parser.add_option('', '--host', default='127.0.0.1',
2286 dest='host',
2287 help='Hostname or IP upon which the server will '
2288 'listen. Client connections will also only be '
2289 'allowed from this address.')
2290 option_parser.add_option('', '--auth-token', dest='auth_token',
2291 help='Specify the auth token which should be used'
2292 'in the authorization header for GData.')
2293 options, args = option_parser.parse_args()
2294
2295 sys.exit(main(options, args))