blob: 62edbf8deba1e3536ed8d05a0cafe1cf755f207a [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
mattm@chromium.org07e28412012-09-05 00:19:41 +000021import hashlib
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000022import httplib
agl@chromium.org77a9ad92012-03-20 15:14:27 +000023import minica
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
agl@chromium.org77a9ad92012-03-20 15:14:27 +000030import sys
31import threading
initial.commit94958cf2008-07-26 22:42:52 +000032import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000033import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000034import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000035import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000036
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000037import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000038import pyftpdlib.ftpserver
mattm@chromium.org07e28412012-09-05 00:19:41 +000039import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000040import tlslite
41import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000042
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000043
mattm@chromium.org07e28412012-09-05 00:19:41 +000044BASE_DIR = os.path.dirname(os.path.abspath(__file__))
davidben@chromium.org06fcf202010-09-22 18:15:23 +000045
maruel@chromium.org756cf982009-03-05 12:46:38 +000046SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000047SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000048SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000049SERVER_TCP_ECHO = 3
50SERVER_UDP_ECHO = 4
initial.commit94958cf2008-07-26 22:42:52 +000051
mattm@chromium.org07e28412012-09-05 00:19:41 +000052
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000053# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000054debug_output = sys.stderr
55def debug(str):
56 debug_output.write(str + "\n")
57 debug_output.flush()
58
mattm@chromium.org07e28412012-09-05 00:19:41 +000059
agl@chromium.orgf9e66792011-12-12 22:22:19 +000060class RecordingSSLSessionCache(object):
61 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
62 lookups and inserts in order to test session cache behaviours."""
63
64 def __init__(self):
65 self.log = []
66
67 def __getitem__(self, sessionID):
68 self.log.append(('lookup', sessionID))
69 raise KeyError()
70
71 def __setitem__(self, sessionID, session):
72 self.log.append(('insert', sessionID))
73
erikwright@chromium.org847ef282012-02-22 16:41:10 +000074
75class ClientRestrictingServerMixIn:
76 """Implements verify_request to limit connections to our configured IP
77 address."""
78
79 def verify_request(self, request, client_address):
80 return client_address[0] == self.server_address[0]
81
82
initial.commit94958cf2008-07-26 22:42:52 +000083class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +000084 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +000085 to be exited cleanly (by setting its "stop" member to True)."""
86
87 def serve_forever(self):
88 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000089 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000090 while not self.stop:
91 self.handle_request()
92 self.socket.close()
93
erikwright@chromium.org847ef282012-02-22 16:41:10 +000094
95class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +000096 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +000097 verification."""
98
99 pass
100
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000101class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
102 """This is a specialization of HTTPServer that serves an
103 OCSP response"""
104
105 def serve_forever_on_thread(self):
106 self.thread = threading.Thread(target = self.serve_forever,
107 name = "OCSPServerThread")
108 self.thread.start()
109
110 def stop_serving(self):
111 self.shutdown()
112 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000113
mattm@chromium.org07e28412012-09-05 00:19:41 +0000114
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000115class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
116 ClientRestrictingServerMixIn,
117 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000118 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000119 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000120
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000121 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000122 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000123 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000124 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
125 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000126 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000127 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000128 self.tls_intolerant = tls_intolerant
129
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000130 for ca_file in ssl_client_cas:
131 s = open(ca_file).read()
132 x509 = tlslite.api.X509()
133 x509.parse(s)
134 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000135 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
136 if ssl_bulk_ciphers is not None:
137 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000138
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000139 if record_resume_info:
140 # If record_resume_info is true then we'll replace the session cache with
141 # an object that records the lookups and inserts that it sees.
142 self.session_cache = RecordingSSLSessionCache()
143 else:
144 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000145 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
146
147 def handshake(self, tlsConnection):
148 """Creates the SSL connection."""
149 try:
150 tlsConnection.handshakeServer(certChain=self.cert_chain,
151 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000152 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000153 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000154 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000155 reqCAs=self.ssl_client_cas,
156 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000157 tlsConnection.ignoreAbruptClose = True
158 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000159 except tlslite.api.TLSAbruptCloseError:
160 # Ignore abrupt close.
161 return True
initial.commit94958cf2008-07-26 22:42:52 +0000162 except tlslite.api.TLSError, error:
163 print "Handshake failure:", str(error)
164 return False
165
akalin@chromium.org154bb132010-11-12 02:20:27 +0000166
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000167class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000168 """An HTTP server that handles sync commands."""
169
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000170 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000171 # We import here to avoid pulling in chromiumsync's dependencies
172 # unless strictly necessary.
173 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000174 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000175 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000176 self._sync_handler = chromiumsync.TestServer()
177 self._xmpp_socket_map = {}
178 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000179 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000180 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000181 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000182
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000183 def GetXmppServer(self):
184 return self._xmpp_server
185
akalin@chromium.org154bb132010-11-12 02:20:27 +0000186 def HandleCommand(self, query, raw_request):
187 return self._sync_handler.HandleCommand(query, raw_request)
188
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000189 def HandleRequestNoBlock(self):
190 """Handles a single request.
191
192 Copied from SocketServer._handle_request_noblock().
193 """
194 try:
195 request, client_address = self.get_request()
196 except socket.error:
197 return
198 if self.verify_request(request, client_address):
199 try:
200 self.process_request(request, client_address)
201 except:
202 self.handle_error(request, client_address)
203 self.close_request(request)
204
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000205 def SetAuthenticated(self, auth_valid):
206 self.authenticated = auth_valid
207
208 def GetAuthenticated(self):
209 return self.authenticated
210
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000211 def serve_forever(self):
212 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
213 """
214
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000215 def HandleXmppSocket(fd, socket_map, handler):
216 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000217
218 Adapted from asyncore.read() et al.
219 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000220 xmpp_connection = socket_map.get(fd)
221 # This could happen if a previous handler call caused fd to get
222 # removed from socket_map.
223 if xmpp_connection is None:
224 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000225 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000226 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000227 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
228 raise
229 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000230 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000231
232 while True:
233 read_fds = [ self.fileno() ]
234 write_fds = []
235 exceptional_fds = []
236
237 for fd, xmpp_connection in self._xmpp_socket_map.items():
238 is_r = xmpp_connection.readable()
239 is_w = xmpp_connection.writable()
240 if is_r:
241 read_fds.append(fd)
242 if is_w:
243 write_fds.append(fd)
244 if is_r or is_w:
245 exceptional_fds.append(fd)
246
247 try:
248 read_fds, write_fds, exceptional_fds = (
249 select.select(read_fds, write_fds, exceptional_fds))
250 except select.error, err:
251 if err.args[0] != errno.EINTR:
252 raise
253 else:
254 continue
255
256 for fd in read_fds:
257 if fd == self.fileno():
258 self.HandleRequestNoBlock()
259 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000260 HandleXmppSocket(fd, self._xmpp_socket_map,
261 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000262
263 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000264 HandleXmppSocket(fd, self._xmpp_socket_map,
265 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000266
267 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000268 HandleXmppSocket(fd, self._xmpp_socket_map,
269 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000270
akalin@chromium.org154bb132010-11-12 02:20:27 +0000271
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000272class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
273 """This is a specialization of FTPServer that adds client verification."""
274
275 pass
276
277
278class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000279 """A TCP echo server that echoes back what it has received."""
280
281 def server_bind(self):
282 """Override server_bind to store the server name."""
283 SocketServer.TCPServer.server_bind(self)
284 host, port = self.socket.getsockname()[:2]
285 self.server_name = socket.getfqdn(host)
286 self.server_port = port
287
288 def serve_forever(self):
289 self.stop = False
290 self.nonce_time = None
291 while not self.stop:
292 self.handle_request()
293 self.socket.close()
294
295
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000296class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000297 """A UDP echo server that echoes back what it has received."""
298
299 def server_bind(self):
300 """Override server_bind to store the server name."""
301 SocketServer.UDPServer.server_bind(self)
302 host, port = self.socket.getsockname()[:2]
303 self.server_name = socket.getfqdn(host)
304 self.server_port = port
305
306 def serve_forever(self):
307 self.stop = False
308 self.nonce_time = None
309 while not self.stop:
310 self.handle_request()
311 self.socket.close()
312
313
akalin@chromium.org154bb132010-11-12 02:20:27 +0000314class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
315
316 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000317 connect_handlers, get_handlers, head_handlers, post_handlers,
318 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000319 self._connect_handlers = connect_handlers
320 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000321 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000322 self._post_handlers = post_handlers
323 self._put_handlers = put_handlers
324 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
325 self, request, client_address, socket_server)
326
327 def log_request(self, *args, **kwargs):
328 # Disable request logging to declutter test log output.
329 pass
330
331 def _ShouldHandleRequest(self, handler_name):
332 """Determines if the path can be handled by the handler.
333
334 We consider a handler valid if the path begins with the
335 handler name. It can optionally be followed by "?*", "/*".
336 """
337
338 pattern = re.compile('%s($|\?|/).*' % handler_name)
339 return pattern.match(self.path)
340
341 def do_CONNECT(self):
342 for handler in self._connect_handlers:
343 if handler():
344 return
345
346 def do_GET(self):
347 for handler in self._get_handlers:
348 if handler():
349 return
350
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000351 def do_HEAD(self):
352 for handler in self._head_handlers:
353 if handler():
354 return
355
akalin@chromium.org154bb132010-11-12 02:20:27 +0000356 def do_POST(self):
357 for handler in self._post_handlers:
358 if handler():
359 return
360
361 def do_PUT(self):
362 for handler in self._put_handlers:
363 if handler():
364 return
365
366
367class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000368
369 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000370 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000371 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000372 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000373 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000374 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000375 self.NoCacheMaxAgeTimeHandler,
376 self.NoCacheTimeHandler,
377 self.CacheTimeHandler,
378 self.CacheExpiresHandler,
379 self.CacheProxyRevalidateHandler,
380 self.CachePrivateHandler,
381 self.CachePublicHandler,
382 self.CacheSMaxAgeHandler,
383 self.CacheMustRevalidateHandler,
384 self.CacheMustRevalidateMaxAgeHandler,
385 self.CacheNoStoreHandler,
386 self.CacheNoStoreMaxAgeHandler,
387 self.CacheNoTransformHandler,
388 self.DownloadHandler,
389 self.DownloadFinishHandler,
390 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000391 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000392 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000393 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000394 self.GDataAuthHandler,
395 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000396 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000397 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000398 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000399 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000400 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000401 self.AuthBasicHandler,
402 self.AuthDigestHandler,
403 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000404 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000405 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000406 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000407 self.ServerRedirectHandler,
408 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000409 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000410 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000411 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000412 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000414 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000415 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000416 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000417 self.DeviceManagementHandler,
418 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000419 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000420 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000421 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000422 head_handlers = [
423 self.FileHandler,
424 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000427 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000428 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000429 'gif': 'image/gif',
430 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000431 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000432 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000433 'pdf' : 'application/pdf',
434 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000435 }
initial.commit94958cf2008-07-26 22:42:52 +0000436 self._default_mime_type = 'text/html'
437
akalin@chromium.org154bb132010-11-12 02:20:27 +0000438 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000439 connect_handlers, get_handlers, head_handlers,
440 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000441
initial.commit94958cf2008-07-26 22:42:52 +0000442 def GetMIMETypeFromName(self, file_name):
443 """Returns the mime type for the specified file_name. So far it only looks
444 at the file extension."""
445
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000446 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000447 if len(extension) == 0:
448 # no extension.
449 return self._default_mime_type
450
ericroman@google.comc17ca532009-05-07 03:51:05 +0000451 # extension starts with a dot, so we need to remove it
452 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000453
initial.commit94958cf2008-07-26 22:42:52 +0000454 def NoCacheMaxAgeTimeHandler(self):
455 """This request handler yields a page with the title set to the current
456 system time, and no caching requested."""
457
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000458 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000459 return False
460
461 self.send_response(200)
462 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000463 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000464 self.end_headers()
465
maruel@google.come250a9b2009-03-10 17:39:46 +0000466 self.wfile.write('<html><head><title>%s</title></head></html>' %
467 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000468
469 return True
470
471 def NoCacheTimeHandler(self):
472 """This request handler yields a page with the title set to the current
473 system time, and no caching requested."""
474
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000475 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000476 return False
477
478 self.send_response(200)
479 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000480 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.end_headers()
482
maruel@google.come250a9b2009-03-10 17:39:46 +0000483 self.wfile.write('<html><head><title>%s</title></head></html>' %
484 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000485
486 return True
487
488 def CacheTimeHandler(self):
489 """This request handler yields a page with the title set to the current
490 system time, and allows caching for one minute."""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
496 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000497 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000498 self.end_headers()
499
maruel@google.come250a9b2009-03-10 17:39:46 +0000500 self.wfile.write('<html><head><title>%s</title></head></html>' %
501 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000502
503 return True
504
505 def CacheExpiresHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and set the page to expire on 1 Jan 2099."""
508
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000509 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000510 return False
511
512 self.send_response(200)
513 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000514 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000515 self.end_headers()
516
maruel@google.come250a9b2009-03-10 17:39:46 +0000517 self.wfile.write('<html><head><title>%s</title></head></html>' %
518 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000519
520 return True
521
522 def CacheProxyRevalidateHandler(self):
523 """This request handler yields a page with the title set to the current
524 system time, and allows caching for 60 seconds"""
525
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000526 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000527 return False
528
529 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000530 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000531 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
532 self.end_headers()
533
maruel@google.come250a9b2009-03-10 17:39:46 +0000534 self.wfile.write('<html><head><title>%s</title></head></html>' %
535 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000536
537 return True
538
539 def CachePrivateHandler(self):
540 """This request handler yields a page with the title set to the current
541 system time, and allows caching for 5 seconds."""
542
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000543 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000544 return False
545
546 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000547 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000548 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000549 self.end_headers()
550
maruel@google.come250a9b2009-03-10 17:39:46 +0000551 self.wfile.write('<html><head><title>%s</title></head></html>' %
552 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000553
554 return True
555
556 def CachePublicHandler(self):
557 """This request handler yields a page with the title set to the current
558 system time, and allows caching for 5 seconds."""
559
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000560 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000561 return False
562
563 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000564 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000565 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000566 self.end_headers()
567
maruel@google.come250a9b2009-03-10 17:39:46 +0000568 self.wfile.write('<html><head><title>%s</title></head></html>' %
569 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000570
571 return True
572
573 def CacheSMaxAgeHandler(self):
574 """This request handler yields a page with the title set to the current
575 system time, and does not allow for caching."""
576
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000577 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000578 return False
579
580 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000581 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000582 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
583 self.end_headers()
584
maruel@google.come250a9b2009-03-10 17:39:46 +0000585 self.wfile.write('<html><head><title>%s</title></head></html>' %
586 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000587
588 return True
589
590 def CacheMustRevalidateHandler(self):
591 """This request handler yields a page with the title set to the current
592 system time, and does not allow caching."""
593
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000594 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000595 return False
596
597 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000598 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000599 self.send_header('Cache-Control', 'must-revalidate')
600 self.end_headers()
601
maruel@google.come250a9b2009-03-10 17:39:46 +0000602 self.wfile.write('<html><head><title>%s</title></head></html>' %
603 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000604
605 return True
606
607 def CacheMustRevalidateMaxAgeHandler(self):
608 """This request handler yields a page with the title set to the current
609 system time, and does not allow caching event though max-age of 60
610 seconds is specified."""
611
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000612 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000613 return False
614
615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000616 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000617 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
618 self.end_headers()
619
maruel@google.come250a9b2009-03-10 17:39:46 +0000620 self.wfile.write('<html><head><title>%s</title></head></html>' %
621 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000622
623 return True
624
initial.commit94958cf2008-07-26 22:42:52 +0000625 def CacheNoStoreHandler(self):
626 """This request handler yields a page with the title set to the current
627 system time, and does not allow the page to be stored."""
628
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000629 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000630 return False
631
632 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000633 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000634 self.send_header('Cache-Control', 'no-store')
635 self.end_headers()
636
maruel@google.come250a9b2009-03-10 17:39:46 +0000637 self.wfile.write('<html><head><title>%s</title></head></html>' %
638 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000639
640 return True
641
642 def CacheNoStoreMaxAgeHandler(self):
643 """This request handler yields a page with the title set to the current
644 system time, and does not allow the page to be stored even though max-age
645 of 60 seconds is specified."""
646
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000647 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000648 return False
649
650 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000651 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000652 self.send_header('Cache-Control', 'max-age=60, no-store')
653 self.end_headers()
654
maruel@google.come250a9b2009-03-10 17:39:46 +0000655 self.wfile.write('<html><head><title>%s</title></head></html>' %
656 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000657
658 return True
659
660
661 def CacheNoTransformHandler(self):
662 """This request handler yields a page with the title set to the current
663 system time, and does not allow the content to transformed during
664 user-agent caching"""
665
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000666 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000667 return False
668
669 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000670 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000671 self.send_header('Cache-Control', 'no-transform')
672 self.end_headers()
673
maruel@google.come250a9b2009-03-10 17:39:46 +0000674 self.wfile.write('<html><head><title>%s</title></head></html>' %
675 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000676
677 return True
678
679 def EchoHeader(self):
680 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000681 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000682
ananta@chromium.org56812d02011-04-07 17:52:05 +0000683 """This function echoes back the value of a specific request header"""
684 """while allowing caching for 16 hours."""
685 def EchoHeaderCache(self):
686 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000687
688 def EchoHeaderHelper(self, echo_header):
689 """This function echoes back the value of the request header passed in."""
690 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000691 return False
692
693 query_char = self.path.find('?')
694 if query_char != -1:
695 header_name = self.path[query_char+1:]
696
697 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000698 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000699 if echo_header == '/echoheadercache':
700 self.send_header('Cache-control', 'max-age=60000')
701 else:
702 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000703 # insert a vary header to properly indicate that the cachability of this
704 # request is subject to value of the request header being echoed.
705 if len(header_name) > 0:
706 self.send_header('Vary', header_name)
707 self.end_headers()
708
709 if len(header_name) > 0:
710 self.wfile.write(self.headers.getheader(header_name))
711
712 return True
713
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000714 def ReadRequestBody(self):
715 """This function reads the body of the current HTTP request, handling
716 both plain and chunked transfer encoded requests."""
717
718 if self.headers.getheader('transfer-encoding') != 'chunked':
719 length = int(self.headers.getheader('content-length'))
720 return self.rfile.read(length)
721
722 # Read the request body as chunks.
723 body = ""
724 while True:
725 line = self.rfile.readline()
726 length = int(line, 16)
727 if length == 0:
728 self.rfile.readline()
729 break
730 body += self.rfile.read(length)
731 self.rfile.read(2)
732 return body
733
initial.commit94958cf2008-07-26 22:42:52 +0000734 def EchoHandler(self):
735 """This handler just echoes back the payload of the request, for testing
736 form submission."""
737
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000738 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000739 return False
740
741 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000742 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000743 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000744 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000745 return True
746
747 def EchoTitleHandler(self):
748 """This handler is like Echo, but sets the page title to the request."""
749
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000750 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000751 return False
752
753 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000754 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000755 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000756 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000757 self.wfile.write('<html><head><title>')
758 self.wfile.write(request)
759 self.wfile.write('</title></head></html>')
760 return True
761
762 def EchoAllHandler(self):
763 """This handler yields a (more) human-readable page listing information
764 about the request header & contents."""
765
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000766 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000767 return False
768
769 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000770 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000771 self.end_headers()
772 self.wfile.write('<html><head><style>'
773 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
774 '</style></head><body>'
775 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000776 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000777 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000778
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000779 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000780 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000781 params = cgi.parse_qs(qs, keep_blank_values=1)
782
783 for param in params:
784 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000785
786 self.wfile.write('</pre>')
787
788 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
789
790 self.wfile.write('</body></html>')
791 return True
792
793 def DownloadHandler(self):
794 """This handler sends a downloadable file with or without reporting
795 the size (6K)."""
796
797 if self.path.startswith("/download-unknown-size"):
798 send_length = False
799 elif self.path.startswith("/download-known-size"):
800 send_length = True
801 else:
802 return False
803
804 #
805 # The test which uses this functionality is attempting to send
806 # small chunks of data to the client. Use a fairly large buffer
807 # so that we'll fill chrome's IO buffer enough to force it to
808 # actually write the data.
809 # See also the comments in the client-side of this test in
810 # download_uitest.cc
811 #
812 size_chunk1 = 35*1024
813 size_chunk2 = 10*1024
814
815 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000816 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000817 self.send_header('Cache-Control', 'max-age=0')
818 if send_length:
819 self.send_header('Content-Length', size_chunk1 + size_chunk2)
820 self.end_headers()
821
822 # First chunk of data:
823 self.wfile.write("*" * size_chunk1)
824 self.wfile.flush()
825
826 # handle requests until one of them clears this flag.
827 self.server.waitForDownload = True
828 while self.server.waitForDownload:
829 self.server.handle_request()
830
831 # Second chunk of data:
832 self.wfile.write("*" * size_chunk2)
833 return True
834
835 def DownloadFinishHandler(self):
836 """This handler just tells the server to finish the current download."""
837
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000838 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000839 return False
840
841 self.server.waitForDownload = False
842 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000843 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000844 self.send_header('Cache-Control', 'max-age=0')
845 self.end_headers()
846 return True
847
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000848 def _ReplaceFileData(self, data, query_parameters):
849 """Replaces matching substrings in a file.
850
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000851 If the 'replace_text' URL query parameter is present, it is expected to be
852 of the form old_text:new_text, which indicates that any old_text strings in
853 the file are replaced with new_text. Multiple 'replace_text' parameters may
854 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000855
856 If the parameters are not present, |data| is returned.
857 """
858 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000859 replace_text_values = query_dict.get('replace_text', [])
860 for replace_text_value in replace_text_values:
861 replace_text_args = replace_text_value.split(':')
862 if len(replace_text_args) != 2:
863 raise ValueError(
864 'replace_text must be of form old_text:new_text. Actual value: %s' %
865 replace_text_value)
866 old_text_b64, new_text_b64 = replace_text_args
867 old_text = base64.urlsafe_b64decode(old_text_b64)
868 new_text = base64.urlsafe_b64decode(new_text_b64)
869 data = data.replace(old_text, new_text)
870 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000871
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000872 def ZipFileHandler(self):
873 """This handler sends the contents of the requested file in compressed form.
874 Can pass in a parameter that specifies that the content length be
875 C - the compressed size (OK),
876 U - the uncompressed size (Non-standard, but handled),
877 S - less than compressed (OK because we keep going),
878 M - larger than compressed but less than uncompressed (an error),
879 L - larger than uncompressed (an error)
880 Example: compressedfiles/Picture_1.doc?C
881 """
882
883 prefix = "/compressedfiles/"
884 if not self.path.startswith(prefix):
885 return False
886
887 # Consume a request body if present.
888 if self.command == 'POST' or self.command == 'PUT' :
889 self.ReadRequestBody()
890
891 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
892
893 if not query in ('C', 'U', 'S', 'M', 'L'):
894 return False
895
896 sub_path = url_path[len(prefix):]
897 entries = sub_path.split('/')
898 file_path = os.path.join(self.server.data_dir, *entries)
899 if os.path.isdir(file_path):
900 file_path = os.path.join(file_path, 'index.html')
901
902 if not os.path.isfile(file_path):
903 print "File not found " + sub_path + " full path:" + file_path
904 self.send_error(404)
905 return True
906
907 f = open(file_path, "rb")
908 data = f.read()
909 uncompressed_len = len(data)
910 f.close()
911
912 # Compress the data.
913 data = zlib.compress(data)
914 compressed_len = len(data)
915
916 content_length = compressed_len
917 if query == 'U':
918 content_length = uncompressed_len
919 elif query == 'S':
920 content_length = compressed_len / 2
921 elif query == 'M':
922 content_length = (compressed_len + uncompressed_len) / 2
923 elif query == 'L':
924 content_length = compressed_len + uncompressed_len
925
926 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000927 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000928 self.send_header('Content-encoding', 'deflate')
929 self.send_header('Connection', 'close')
930 self.send_header('Content-Length', content_length)
931 self.send_header('ETag', '\'' + file_path + '\'')
932 self.end_headers()
933
934 self.wfile.write(data)
935
936 return True
937
initial.commit94958cf2008-07-26 22:42:52 +0000938 def FileHandler(self):
939 """This handler sends the contents of the requested file. Wow, it's like
940 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000941 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000942 if not self.path.startswith(prefix):
943 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000944 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000945
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000946 def PostOnlyFileHandler(self):
947 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000948 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000949 if not self.path.startswith(prefix):
950 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000951 return self._FileHandlerHelper(prefix)
952
953 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000954 request_body = ''
955 if self.command == 'POST' or self.command == 'PUT':
956 # Consume a request body if present.
957 request_body = self.ReadRequestBody()
958
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000959 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000960 query_dict = cgi.parse_qs(query)
961
962 expected_body = query_dict.get('expected_body', [])
963 if expected_body and request_body not in expected_body:
964 self.send_response(404)
965 self.end_headers()
966 self.wfile.write('')
967 return True
968
969 expected_headers = query_dict.get('expected_headers', [])
970 for expected_header in expected_headers:
971 header_name, expected_value = expected_header.split(':')
972 if self.headers.getheader(header_name) != expected_value:
973 self.send_response(404)
974 self.end_headers()
975 self.wfile.write('')
976 return True
977
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000978 sub_path = url_path[len(prefix):]
979 entries = sub_path.split('/')
980 file_path = os.path.join(self.server.data_dir, *entries)
981 if os.path.isdir(file_path):
982 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000983
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000984 if not os.path.isfile(file_path):
985 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000986 self.send_error(404)
987 return True
988
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000989 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000990 data = f.read()
991 f.close()
992
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000993 data = self._ReplaceFileData(data, query)
994
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000995 old_protocol_version = self.protocol_version
996
initial.commit94958cf2008-07-26 22:42:52 +0000997 # If file.mock-http-headers exists, it contains the headers we
998 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000999 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001000 if os.path.isfile(headers_path):
1001 f = open(headers_path, "r")
1002
1003 # "HTTP/1.1 200 OK"
1004 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001005 http_major, http_minor, status_code = re.findall(
1006 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1007 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001008 self.send_response(int(status_code))
1009
1010 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001011 header_values = re.findall('(\S+):\s*(.*)', line)
1012 if len(header_values) > 0:
1013 # "name: value"
1014 name, value = header_values[0]
1015 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001016 f.close()
1017 else:
1018 # Could be more generic once we support mime-type sniffing, but for
1019 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001020
1021 range = self.headers.get('Range')
1022 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001023 # Note this doesn't handle all valid byte range values (i.e. left
1024 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001025 range = range[6:].split('-')
1026 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001027 if range[1]:
1028 end = int(range[1])
1029 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001030 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001031
1032 self.send_response(206)
1033 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1034 str(len(data))
1035 self.send_header('Content-Range', content_range)
1036 data = data[start: end + 1]
1037 else:
1038 self.send_response(200)
1039
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001040 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001041 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001042 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001043 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001044 self.end_headers()
1045
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001046 if (self.command != 'HEAD'):
1047 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001048
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001049 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001050 return True
1051
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001052 def SetCookieHandler(self):
1053 """This handler just sets a cookie, for testing cookie handling."""
1054
1055 if not self._ShouldHandleRequest("/set-cookie"):
1056 return False
1057
1058 query_char = self.path.find('?')
1059 if query_char != -1:
1060 cookie_values = self.path[query_char + 1:].split('&')
1061 else:
1062 cookie_values = ("",)
1063 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001064 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001065 for cookie_value in cookie_values:
1066 self.send_header('Set-Cookie', '%s' % cookie_value)
1067 self.end_headers()
1068 for cookie_value in cookie_values:
1069 self.wfile.write('%s' % cookie_value)
1070 return True
1071
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001072 def SetManyCookiesHandler(self):
1073 """This handler just sets a given number of cookies, for testing handling
1074 of large numbers of cookies."""
1075
1076 if not self._ShouldHandleRequest("/set-many-cookies"):
1077 return False
1078
1079 query_char = self.path.find('?')
1080 if query_char != -1:
1081 num_cookies = int(self.path[query_char + 1:])
1082 else:
1083 num_cookies = 0
1084 self.send_response(200)
1085 self.send_header('', 'text/html')
1086 for i in range(0, num_cookies):
1087 self.send_header('Set-Cookie', 'a=')
1088 self.end_headers()
1089 self.wfile.write('%d cookies were sent' % num_cookies)
1090 return True
1091
mattm@chromium.org983fc462012-06-30 00:52:08 +00001092 def ExpectAndSetCookieHandler(self):
1093 """Expects some cookies to be sent, and if they are, sets more cookies.
1094
1095 The expect parameter specifies a required cookie. May be specified multiple
1096 times.
1097 The set parameter specifies a cookie to set if all required cookies are
1098 preset. May be specified multiple times.
1099 The data parameter specifies the response body data to be returned."""
1100
1101 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1102 return False
1103
1104 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1105 query_dict = cgi.parse_qs(query)
1106 cookies = set()
1107 if 'Cookie' in self.headers:
1108 cookie_header = self.headers.getheader('Cookie')
1109 cookies.update([s.strip() for s in cookie_header.split(';')])
1110 got_all_expected_cookies = True
1111 for expected_cookie in query_dict.get('expect', []):
1112 if expected_cookie not in cookies:
1113 got_all_expected_cookies = False
1114 self.send_response(200)
1115 self.send_header('Content-Type', 'text/html')
1116 if got_all_expected_cookies:
1117 for cookie_value in query_dict.get('set', []):
1118 self.send_header('Set-Cookie', '%s' % cookie_value)
1119 self.end_headers()
1120 for data_value in query_dict.get('data', []):
1121 self.wfile.write(data_value)
1122 return True
1123
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001124 def SetHeaderHandler(self):
1125 """This handler sets a response header. Parameters are in the
1126 key%3A%20value&key2%3A%20value2 format."""
1127
1128 if not self._ShouldHandleRequest("/set-header"):
1129 return False
1130
1131 query_char = self.path.find('?')
1132 if query_char != -1:
1133 headers_values = self.path[query_char + 1:].split('&')
1134 else:
1135 headers_values = ("",)
1136 self.send_response(200)
1137 self.send_header('Content-Type', 'text/html')
1138 for header_value in headers_values:
1139 header_value = urllib.unquote(header_value)
1140 (key, value) = header_value.split(': ', 1)
1141 self.send_header(key, value)
1142 self.end_headers()
1143 for header_value in headers_values:
1144 self.wfile.write('%s' % header_value)
1145 return True
1146
initial.commit94958cf2008-07-26 22:42:52 +00001147 def AuthBasicHandler(self):
1148 """This handler tests 'Basic' authentication. It just sends a page with
1149 title 'user/pass' if you succeed."""
1150
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001151 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001152 return False
1153
1154 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001155 expected_password = 'secret'
1156 realm = 'testrealm'
1157 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001158
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001159 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1160 query_params = cgi.parse_qs(query, True)
1161 if 'set-cookie-if-challenged' in query_params:
1162 set_cookie_if_challenged = True
1163 if 'password' in query_params:
1164 expected_password = query_params['password'][0]
1165 if 'realm' in query_params:
1166 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001167
initial.commit94958cf2008-07-26 22:42:52 +00001168 auth = self.headers.getheader('authorization')
1169 try:
1170 if not auth:
1171 raise Exception('no auth')
1172 b64str = re.findall(r'Basic (\S+)', auth)[0]
1173 userpass = base64.b64decode(b64str)
1174 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001175 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001176 raise Exception('wrong password')
1177 except Exception, e:
1178 # Authentication failed.
1179 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001180 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001181 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001182 if set_cookie_if_challenged:
1183 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001184 self.end_headers()
1185 self.wfile.write('<html><head>')
1186 self.wfile.write('<title>Denied: %s</title>' % e)
1187 self.wfile.write('</head><body>')
1188 self.wfile.write('auth=%s<p>' % auth)
1189 self.wfile.write('b64str=%s<p>' % b64str)
1190 self.wfile.write('username: %s<p>' % username)
1191 self.wfile.write('userpass: %s<p>' % userpass)
1192 self.wfile.write('password: %s<p>' % password)
1193 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1194 self.wfile.write('</body></html>')
1195 return True
1196
1197 # Authentication successful. (Return a cachable response to allow for
1198 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001199 old_protocol_version = self.protocol_version
1200 self.protocol_version = "HTTP/1.1"
1201
initial.commit94958cf2008-07-26 22:42:52 +00001202 if_none_match = self.headers.getheader('if-none-match')
1203 if if_none_match == "abc":
1204 self.send_response(304)
1205 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001206 elif url_path.endswith(".gif"):
1207 # Using chrome/test/data/google/logo.gif as the test image
1208 test_image_path = ['google', 'logo.gif']
1209 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1210 if not os.path.isfile(gif_path):
1211 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001212 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001213 return True
1214
1215 f = open(gif_path, "rb")
1216 data = f.read()
1217 f.close()
1218
1219 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001220 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001221 self.send_header('Cache-control', 'max-age=60000')
1222 self.send_header('Etag', 'abc')
1223 self.end_headers()
1224 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001225 else:
1226 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001227 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001228 self.send_header('Cache-control', 'max-age=60000')
1229 self.send_header('Etag', 'abc')
1230 self.end_headers()
1231 self.wfile.write('<html><head>')
1232 self.wfile.write('<title>%s/%s</title>' % (username, password))
1233 self.wfile.write('</head><body>')
1234 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001235 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001236 self.wfile.write('</body></html>')
1237
rvargas@google.com54453b72011-05-19 01:11:11 +00001238 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001239 return True
1240
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001241 def GDataAuthHandler(self):
1242 """This handler verifies the Authentication header for GData requests."""
1243 if not self.server.gdata_auth_token:
1244 # --auth-token is not specified, not the test case for GData.
1245 return False
1246
1247 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1248 return False
1249
1250 if 'GData-Version' not in self.headers:
1251 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1252 return True
1253
1254 if 'Authorization' not in self.headers:
1255 self.send_error(httplib.UNAUTHORIZED)
1256 return True
1257
1258 field_prefix = 'Bearer '
1259 authorization = self.headers['Authorization']
1260 if not authorization.startswith(field_prefix):
1261 self.send_error(httplib.UNAUTHORIZED)
1262 return True
1263
1264 code = authorization[len(field_prefix):]
1265 if code != self.server.gdata_auth_token:
1266 self.send_error(httplib.UNAUTHORIZED)
1267 return True
1268
1269 return False
1270
1271 def GDataDocumentsFeedQueryHandler(self):
1272 """This handler verifies if required parameters are properly
1273 specified for the GData DocumentsFeed request."""
1274 if not self.server.gdata_auth_token:
1275 # --auth-token is not specified, not the test case for GData.
1276 return False
1277
1278 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1279 return False
1280
1281 (path, question, query_params) = self.path.partition('?')
1282 self.query_params = urlparse.parse_qs(query_params)
1283
1284 if 'v' not in self.query_params:
1285 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1286 return True
1287 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1288 # currently our GData client only uses JSON format.
1289 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1290 return True
1291
1292 return False
1293
tonyg@chromium.org75054202010-03-31 22:06:10 +00001294 def GetNonce(self, force_reset=False):
1295 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001296
tonyg@chromium.org75054202010-03-31 22:06:10 +00001297 This is a fake implementation. A real implementation would only use a given
1298 nonce a single time (hence the name n-once). However, for the purposes of
1299 unittesting, we don't care about the security of the nonce.
1300
1301 Args:
1302 force_reset: Iff set, the nonce will be changed. Useful for testing the
1303 "stale" response.
1304 """
1305 if force_reset or not self.server.nonce_time:
1306 self.server.nonce_time = time.time()
mattm@chromium.org07e28412012-09-05 00:19:41 +00001307 return hashlib.md5('privatekey%s%d' %
1308 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001309
1310 def AuthDigestHandler(self):
1311 """This handler tests 'Digest' authentication.
1312
1313 It just sends a page with title 'user/pass' if you succeed.
1314
1315 A stale response is sent iff "stale" is present in the request path.
1316 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001317 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001318 return False
1319
tonyg@chromium.org75054202010-03-31 22:06:10 +00001320 stale = 'stale' in self.path
1321 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org07e28412012-09-05 00:19:41 +00001322 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001323 password = 'secret'
1324 realm = 'testrealm'
1325
1326 auth = self.headers.getheader('authorization')
1327 pairs = {}
1328 try:
1329 if not auth:
1330 raise Exception('no auth')
1331 if not auth.startswith('Digest'):
1332 raise Exception('not digest')
1333 # Pull out all the name="value" pairs as a dictionary.
1334 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1335
1336 # Make sure it's all valid.
1337 if pairs['nonce'] != nonce:
1338 raise Exception('wrong nonce')
1339 if pairs['opaque'] != opaque:
1340 raise Exception('wrong opaque')
1341
1342 # Check the 'response' value and make sure it matches our magic hash.
1343 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org07e28412012-09-05 00:19:41 +00001344 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001345 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org07e28412012-09-05 00:19:41 +00001346 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001347 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org07e28412012-09-05 00:19:41 +00001348 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001349 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1350 else:
mattm@chromium.org07e28412012-09-05 00:19:41 +00001351 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001352
1353 if pairs['response'] != response:
1354 raise Exception('wrong password')
1355 except Exception, e:
1356 # Authentication failed.
1357 self.send_response(401)
1358 hdr = ('Digest '
1359 'realm="%s", '
1360 'domain="/", '
1361 'qop="auth", '
1362 'algorithm=MD5, '
1363 'nonce="%s", '
1364 'opaque="%s"') % (realm, nonce, opaque)
1365 if stale:
1366 hdr += ', stale="TRUE"'
1367 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001368 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001369 self.end_headers()
1370 self.wfile.write('<html><head>')
1371 self.wfile.write('<title>Denied: %s</title>' % e)
1372 self.wfile.write('</head><body>')
1373 self.wfile.write('auth=%s<p>' % auth)
1374 self.wfile.write('pairs=%s<p>' % pairs)
1375 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1376 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1377 self.wfile.write('</body></html>')
1378 return True
1379
1380 # Authentication successful.
1381 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001382 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001383 self.end_headers()
1384 self.wfile.write('<html><head>')
1385 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1386 self.wfile.write('</head><body>')
1387 self.wfile.write('auth=%s<p>' % auth)
1388 self.wfile.write('pairs=%s<p>' % pairs)
1389 self.wfile.write('</body></html>')
1390
1391 return True
1392
1393 def SlowServerHandler(self):
1394 """Wait for the user suggested time before responding. The syntax is
1395 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001396 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001397 return False
1398 query_char = self.path.find('?')
1399 wait_sec = 1.0
1400 if query_char >= 0:
1401 try:
1402 wait_sec = int(self.path[query_char + 1:])
1403 except ValueError:
1404 pass
1405 time.sleep(wait_sec)
1406 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001407 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001408 self.end_headers()
1409 self.wfile.write("waited %d seconds" % wait_sec)
1410 return True
1411
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001412 def ChunkedServerHandler(self):
1413 """Send chunked response. Allows to specify chunks parameters:
1414 - waitBeforeHeaders - ms to wait before sending headers
1415 - waitBetweenChunks - ms to wait between chunks
1416 - chunkSize - size of each chunk in bytes
1417 - chunksNumber - number of chunks
1418 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1419 waits one second, then sends headers and five chunks five bytes each."""
1420 if not self._ShouldHandleRequest("/chunked"):
1421 return False
1422 query_char = self.path.find('?')
1423 chunkedSettings = {'waitBeforeHeaders' : 0,
1424 'waitBetweenChunks' : 0,
1425 'chunkSize' : 5,
1426 'chunksNumber' : 5}
1427 if query_char >= 0:
1428 params = self.path[query_char + 1:].split('&')
1429 for param in params:
1430 keyValue = param.split('=')
1431 if len(keyValue) == 2:
1432 try:
1433 chunkedSettings[keyValue[0]] = int(keyValue[1])
1434 except ValueError:
1435 pass
1436 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1437 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1438 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001439 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001440 self.send_header('Connection', 'close')
1441 self.send_header('Transfer-Encoding', 'chunked')
1442 self.end_headers()
1443 # Chunked encoding: sending all chunks, then final zero-length chunk and
1444 # then final CRLF.
1445 for i in range(0, chunkedSettings['chunksNumber']):
1446 if i > 0:
1447 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1448 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1449 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1450 self.sendChunkHelp('')
1451 return True
1452
initial.commit94958cf2008-07-26 22:42:52 +00001453 def ContentTypeHandler(self):
1454 """Returns a string of html with the given content type. E.g.,
1455 /contenttype?text/css returns an html file with the Content-Type
1456 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001457 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001458 return False
1459 query_char = self.path.find('?')
1460 content_type = self.path[query_char + 1:].strip()
1461 if not content_type:
1462 content_type = 'text/html'
1463 self.send_response(200)
1464 self.send_header('Content-Type', content_type)
1465 self.end_headers()
1466 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1467 return True
1468
creis@google.com2f4f6a42011-03-25 19:44:19 +00001469 def NoContentHandler(self):
1470 """Returns a 204 No Content response."""
1471 if not self._ShouldHandleRequest("/nocontent"):
1472 return False
1473 self.send_response(204)
1474 self.end_headers()
1475 return True
1476
initial.commit94958cf2008-07-26 22:42:52 +00001477 def ServerRedirectHandler(self):
1478 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001479 '/server-redirect?http://foo.bar/asdf' to redirect to
1480 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001481
1482 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001483 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001484 return False
1485
1486 query_char = self.path.find('?')
1487 if query_char < 0 or len(self.path) <= query_char + 1:
1488 self.sendRedirectHelp(test_name)
1489 return True
1490 dest = self.path[query_char + 1:]
1491
1492 self.send_response(301) # moved permanently
1493 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001494 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001495 self.end_headers()
1496 self.wfile.write('<html><head>')
1497 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1498
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001499 return True
initial.commit94958cf2008-07-26 22:42:52 +00001500
1501 def ClientRedirectHandler(self):
1502 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001503 '/client-redirect?http://foo.bar/asdf' to redirect to
1504 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001505
1506 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001507 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001508 return False
1509
1510 query_char = self.path.find('?');
1511 if query_char < 0 or len(self.path) <= query_char + 1:
1512 self.sendRedirectHelp(test_name)
1513 return True
1514 dest = self.path[query_char + 1:]
1515
1516 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001517 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001518 self.end_headers()
1519 self.wfile.write('<html><head>')
1520 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1521 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1522
1523 return True
1524
tony@chromium.org03266982010-03-05 03:18:42 +00001525 def MultipartHandler(self):
1526 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001527 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001528 if not self._ShouldHandleRequest(test_name):
1529 return False
1530
1531 num_frames = 10
1532 bound = '12345'
1533 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001534 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001535 'multipart/x-mixed-replace;boundary=' + bound)
1536 self.end_headers()
1537
1538 for i in xrange(num_frames):
1539 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001540 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001541 self.wfile.write('<title>page ' + str(i) + '</title>')
1542 self.wfile.write('page ' + str(i))
1543
1544 self.wfile.write('--' + bound + '--')
1545 return True
1546
tony@chromium.org4cb88302011-09-27 22:13:49 +00001547 def MultipartSlowHandler(self):
1548 """Send a multipart response (3 text/html pages) with a slight delay
1549 between each page. This is similar to how some pages show status using
1550 multipart."""
1551 test_name = '/multipart-slow'
1552 if not self._ShouldHandleRequest(test_name):
1553 return False
1554
1555 num_frames = 3
1556 bound = '12345'
1557 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001558 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001559 'multipart/x-mixed-replace;boundary=' + bound)
1560 self.end_headers()
1561
1562 for i in xrange(num_frames):
1563 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001564 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001565 time.sleep(0.25)
1566 if i == 2:
1567 self.wfile.write('<title>PASS</title>')
1568 else:
1569 self.wfile.write('<title>page ' + str(i) + '</title>')
1570 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1571
1572 self.wfile.write('--' + bound + '--')
1573 return True
1574
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001575 def GetSSLSessionCacheHandler(self):
1576 """Send a reply containing a log of the session cache operations."""
1577
1578 if not self._ShouldHandleRequest('/ssl-session-cache'):
1579 return False
1580
1581 self.send_response(200)
1582 self.send_header('Content-Type', 'text/plain')
1583 self.end_headers()
1584 try:
1585 for (action, sessionID) in self.server.session_cache.log:
1586 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1587 except AttributeError, e:
1588 self.wfile.write('Pass --https-record-resume in order to use' +
1589 ' this request')
1590 return True
1591
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001592 def CloseSocketHandler(self):
1593 """Closes the socket without sending anything."""
1594
1595 if not self._ShouldHandleRequest('/close-socket'):
1596 return False
1597
1598 self.wfile.close()
1599 return True
1600
initial.commit94958cf2008-07-26 22:42:52 +00001601 def DefaultResponseHandler(self):
1602 """This is the catch-all response handler for requests that aren't handled
1603 by one of the special handlers above.
1604 Note that we specify the content-length as without it the https connection
1605 is not closed properly (and the browser keeps expecting data)."""
1606
1607 contents = "Default response given for path: " + self.path
1608 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001609 self.send_header('Content-Type', 'text/html')
1610 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001611 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001612 if (self.command != 'HEAD'):
1613 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001614 return True
1615
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001616 def RedirectConnectHandler(self):
1617 """Sends a redirect to the CONNECT request for www.redirect.com. This
1618 response is not specified by the RFC, so the browser should not follow
1619 the redirect."""
1620
1621 if (self.path.find("www.redirect.com") < 0):
1622 return False
1623
1624 dest = "http://www.destination.com/foo.js"
1625
1626 self.send_response(302) # moved temporarily
1627 self.send_header('Location', dest)
1628 self.send_header('Connection', 'close')
1629 self.end_headers()
1630 return True
1631
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001632 def ServerAuthConnectHandler(self):
1633 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1634 response doesn't make sense because the proxy server cannot request
1635 server authentication."""
1636
1637 if (self.path.find("www.server-auth.com") < 0):
1638 return False
1639
1640 challenge = 'Basic realm="WallyWorld"'
1641
1642 self.send_response(401) # unauthorized
1643 self.send_header('WWW-Authenticate', challenge)
1644 self.send_header('Connection', 'close')
1645 self.end_headers()
1646 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001647
1648 def DefaultConnectResponseHandler(self):
1649 """This is the catch-all response handler for CONNECT requests that aren't
1650 handled by one of the special handlers above. Real Web servers respond
1651 with 400 to CONNECT requests."""
1652
1653 contents = "Your client has issued a malformed or illegal request."
1654 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001655 self.send_header('Content-Type', 'text/html')
1656 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001657 self.end_headers()
1658 self.wfile.write(contents)
1659 return True
1660
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001661 def DeviceManagementHandler(self):
1662 """Delegates to the device management service used for cloud policy."""
1663 if not self._ShouldHandleRequest("/device_management"):
1664 return False
1665
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001666 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001667
1668 if not self.server._device_management_handler:
1669 import device_management
1670 policy_path = os.path.join(self.server.data_dir, 'device_management')
1671 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001672 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001673 self.server.policy_keys,
1674 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001675
1676 http_response, raw_reply = (
1677 self.server._device_management_handler.HandleRequest(self.path,
1678 self.headers,
1679 raw_request))
1680 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001681 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001682 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001683 self.end_headers()
1684 self.wfile.write(raw_reply)
1685 return True
1686
initial.commit94958cf2008-07-26 22:42:52 +00001687 # called by the redirect handling function when there is no parameter
1688 def sendRedirectHelp(self, redirect_name):
1689 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001690 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001691 self.end_headers()
1692 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1693 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1694 self.wfile.write('</body></html>')
1695
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001696 # called by chunked handling function
1697 def sendChunkHelp(self, chunk):
1698 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1699 self.wfile.write('%X\r\n' % len(chunk))
1700 self.wfile.write(chunk)
1701 self.wfile.write('\r\n')
1702
akalin@chromium.org154bb132010-11-12 02:20:27 +00001703
1704class SyncPageHandler(BasePageHandler):
1705 """Handler for the main HTTP sync server."""
1706
1707 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001708 get_handlers = [self.ChromiumSyncTimeHandler,
1709 self.ChromiumSyncMigrationOpHandler,
1710 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001711 self.ChromiumSyncDisableNotificationsOpHandler,
1712 self.ChromiumSyncEnableNotificationsOpHandler,
1713 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001714 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001715 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001716 self.ChromiumSyncErrorOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001717 self.ChromiumSyncSyncTabsOpHandler,
1718 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001719
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001720 post_handlers = [self.ChromiumSyncCommandHandler,
1721 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001722 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001723 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001724 post_handlers, [])
1725
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001726
akalin@chromium.org154bb132010-11-12 02:20:27 +00001727 def ChromiumSyncTimeHandler(self):
1728 """Handle Chromium sync .../time requests.
1729
1730 The syncer sometimes checks server reachability by examining /time.
1731 """
1732 test_name = "/chromiumsync/time"
1733 if not self._ShouldHandleRequest(test_name):
1734 return False
1735
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001736 # Chrome hates it if we send a response before reading the request.
1737 if self.headers.getheader('content-length'):
1738 length = int(self.headers.getheader('content-length'))
1739 raw_request = self.rfile.read(length)
1740
akalin@chromium.org154bb132010-11-12 02:20:27 +00001741 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001742 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001743 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001744 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001745 return True
1746
1747 def ChromiumSyncCommandHandler(self):
1748 """Handle a chromiumsync command arriving via http.
1749
1750 This covers all sync protocol commands: authentication, getupdates, and
1751 commit.
1752 """
1753 test_name = "/chromiumsync/command"
1754 if not self._ShouldHandleRequest(test_name):
1755 return False
1756
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001757 length = int(self.headers.getheader('content-length'))
1758 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001759 http_response = 200
1760 raw_reply = None
1761 if not self.server.GetAuthenticated():
1762 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001763 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1764 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001765 else:
1766 http_response, raw_reply = self.server.HandleCommand(
1767 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001768
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001769 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001770 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001771 if http_response == 401:
1772 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001773 self.end_headers()
1774 self.wfile.write(raw_reply)
1775 return True
1776
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001777 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001778 test_name = "/chromiumsync/migrate"
1779 if not self._ShouldHandleRequest(test_name):
1780 return False
1781
1782 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1783 self.path)
1784 self.send_response(http_response)
1785 self.send_header('Content-Type', 'text/html')
1786 self.send_header('Content-Length', len(raw_reply))
1787 self.end_headers()
1788 self.wfile.write(raw_reply)
1789 return True
1790
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001791 def ChromiumSyncCredHandler(self):
1792 test_name = "/chromiumsync/cred"
1793 if not self._ShouldHandleRequest(test_name):
1794 return False
1795 try:
1796 query = urlparse.urlparse(self.path)[4]
1797 cred_valid = urlparse.parse_qs(query)['valid']
1798 if cred_valid[0] == 'True':
1799 self.server.SetAuthenticated(True)
1800 else:
1801 self.server.SetAuthenticated(False)
1802 except:
1803 self.server.SetAuthenticated(False)
1804
1805 http_response = 200
1806 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1807 self.send_response(http_response)
1808 self.send_header('Content-Type', 'text/html')
1809 self.send_header('Content-Length', len(raw_reply))
1810 self.end_headers()
1811 self.wfile.write(raw_reply)
1812 return True
1813
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001814 def ChromiumSyncDisableNotificationsOpHandler(self):
1815 test_name = "/chromiumsync/disablenotifications"
1816 if not self._ShouldHandleRequest(test_name):
1817 return False
1818 self.server.GetXmppServer().DisableNotifications()
1819 result = 200
1820 raw_reply = ('<html><title>Notifications disabled</title>'
1821 '<H1>Notifications disabled</H1></html>')
1822 self.send_response(result)
1823 self.send_header('Content-Type', 'text/html')
1824 self.send_header('Content-Length', len(raw_reply))
1825 self.end_headers()
1826 self.wfile.write(raw_reply)
1827 return True;
1828
1829 def ChromiumSyncEnableNotificationsOpHandler(self):
1830 test_name = "/chromiumsync/enablenotifications"
1831 if not self._ShouldHandleRequest(test_name):
1832 return False
1833 self.server.GetXmppServer().EnableNotifications()
1834 result = 200
1835 raw_reply = ('<html><title>Notifications enabled</title>'
1836 '<H1>Notifications enabled</H1></html>')
1837 self.send_response(result)
1838 self.send_header('Content-Type', 'text/html')
1839 self.send_header('Content-Length', len(raw_reply))
1840 self.end_headers()
1841 self.wfile.write(raw_reply)
1842 return True;
1843
1844 def ChromiumSyncSendNotificationOpHandler(self):
1845 test_name = "/chromiumsync/sendnotification"
1846 if not self._ShouldHandleRequest(test_name):
1847 return False
1848 query = urlparse.urlparse(self.path)[4]
1849 query_params = urlparse.parse_qs(query)
1850 channel = ''
1851 data = ''
1852 if 'channel' in query_params:
1853 channel = query_params['channel'][0]
1854 if 'data' in query_params:
1855 data = query_params['data'][0]
1856 self.server.GetXmppServer().SendNotification(channel, data)
1857 result = 200
1858 raw_reply = ('<html><title>Notification sent</title>'
1859 '<H1>Notification sent with channel "%s" '
1860 'and data "%s"</H1></html>'
1861 % (channel, data))
1862 self.send_response(result)
1863 self.send_header('Content-Type', 'text/html')
1864 self.send_header('Content-Length', len(raw_reply))
1865 self.end_headers()
1866 self.wfile.write(raw_reply)
1867 return True;
1868
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001869 def ChromiumSyncBirthdayErrorOpHandler(self):
1870 test_name = "/chromiumsync/birthdayerror"
1871 if not self._ShouldHandleRequest(test_name):
1872 return False
1873 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1874 self.send_response(result)
1875 self.send_header('Content-Type', 'text/html')
1876 self.send_header('Content-Length', len(raw_reply))
1877 self.end_headers()
1878 self.wfile.write(raw_reply)
1879 return True;
1880
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001881 def ChromiumSyncTransientErrorOpHandler(self):
1882 test_name = "/chromiumsync/transienterror"
1883 if not self._ShouldHandleRequest(test_name):
1884 return False
1885 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1886 self.send_response(result)
1887 self.send_header('Content-Type', 'text/html')
1888 self.send_header('Content-Length', len(raw_reply))
1889 self.end_headers()
1890 self.wfile.write(raw_reply)
1891 return True;
1892
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001893 def ChromiumSyncErrorOpHandler(self):
1894 test_name = "/chromiumsync/error"
1895 if not self._ShouldHandleRequest(test_name):
1896 return False
1897 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1898 self.path)
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
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001906 def ChromiumSyncSyncTabsOpHandler(self):
1907 test_name = "/chromiumsync/synctabs"
1908 if not self._ShouldHandleRequest(test_name):
1909 return False
1910 result, raw_reply = self.server._sync_handler.HandleSetSyncTabs()
1911 self.send_response(result)
1912 self.send_header('Content-Type', 'text/html')
1913 self.send_header('Content-Length', len(raw_reply))
1914 self.end_headers()
1915 self.wfile.write(raw_reply)
1916 return True;
1917
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001918 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1919 test_name = "/chromiumsync/createsyncedbookmarks"
1920 if not self._ShouldHandleRequest(test_name):
1921 return False
1922 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1923 self.send_response(result)
1924 self.send_header('Content-Type', 'text/html')
1925 self.send_header('Content-Length', len(raw_reply))
1926 self.end_headers()
1927 self.wfile.write(raw_reply)
1928 return True;
1929
akalin@chromium.org154bb132010-11-12 02:20:27 +00001930
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001931class OCSPHandler(BasePageHandler):
1932 def __init__(self, request, client_address, socket_server):
1933 handlers = [self.OCSPResponse]
1934 self.ocsp_response = socket_server.ocsp_response
1935 BasePageHandler.__init__(self, request, client_address, socket_server,
1936 [], handlers, [], handlers, [])
1937
1938 def OCSPResponse(self):
1939 self.send_response(200)
1940 self.send_header('Content-Type', 'application/ocsp-response')
1941 self.send_header('Content-Length', str(len(self.ocsp_response)))
1942 self.end_headers()
1943
1944 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001945
1946class TCPEchoHandler(SocketServer.BaseRequestHandler):
1947 """The RequestHandler class for TCP echo server.
1948
1949 It is instantiated once per connection to the server, and overrides the
1950 handle() method to implement communication to the client.
1951 """
1952
1953 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001954 """Handles the request from the client and constructs a response."""
1955
1956 data = self.request.recv(65536).strip()
1957 # Verify the "echo request" message received from the client. Send back
1958 # "echo response" message if "echo request" message is valid.
1959 try:
1960 return_data = echo_message.GetEchoResponseData(data)
1961 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001962 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001963 except ValueError:
1964 return
1965
1966 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001967
1968
1969class UDPEchoHandler(SocketServer.BaseRequestHandler):
1970 """The RequestHandler class for UDP echo server.
1971
1972 It is instantiated once per connection to the server, and overrides the
1973 handle() method to implement communication to the client.
1974 """
1975
1976 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001977 """Handles the request from the client and constructs a response."""
1978
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001979 data = self.request[0].strip()
1980 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001981 # Verify the "echo request" message received from the client. Send back
1982 # "echo response" message if "echo request" message is valid.
1983 try:
1984 return_data = echo_message.GetEchoResponseData(data)
1985 if not return_data:
1986 return
1987 except ValueError:
1988 return
1989 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001990
1991
mattm@chromium.org07e28412012-09-05 00:19:41 +00001992class ServerRunner(testserver_base.TestServerRunner):
1993 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001994
mattm@chromium.org07e28412012-09-05 00:19:41 +00001995 def __init__(self):
1996 super(ServerRunner, self).__init__()
1997 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001998
mattm@chromium.org07e28412012-09-05 00:19:41 +00001999 def __make_data_dir(self):
2000 if self.options.data_dir:
2001 if not os.path.isdir(self.options.data_dir):
2002 raise testserver_base.OptionError('specified data dir not found: ' +
2003 self.options.data_dir + ' exiting...')
2004 my_data_dir = self.options.data_dir
2005 else:
2006 # Create the default path to our data dir, relative to the exe dir.
2007 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2008 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002009
mattm@chromium.org07e28412012-09-05 00:19:41 +00002010 #TODO(ibrar): Must use Find* funtion defined in google\tools
2011 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002012
mattm@chromium.org07e28412012-09-05 00:19:41 +00002013 return my_data_dir
initial.commit94958cf2008-07-26 22:42:52 +00002014
mattm@chromium.org07e28412012-09-05 00:19:41 +00002015 def create_server(self, server_data):
2016 port = self.options.port
2017 host = self.options.host
initial.commit94958cf2008-07-26 22:42:52 +00002018
mattm@chromium.org07e28412012-09-05 00:19:41 +00002019 if self.options.server_type == SERVER_HTTP:
2020 if self.options.https:
2021 pem_cert_and_key = None
2022 if self.options.cert_and_key_file:
2023 if not os.path.isfile(self.options.cert_and_key_file):
2024 raise testserver_base.OptionError(
2025 'specified server cert file not found: ' +
2026 self.options.cert_and_key_file + ' exiting...')
2027 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002028 else:
mattm@chromium.org07e28412012-09-05 00:19:41 +00002029 # generate a new certificate and run an OCSP server for it.
2030 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2031 print ('OCSP server started on %s:%d...' %
2032 (host, self.__ocsp_server.server_port))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002033
mattm@chromium.org07e28412012-09-05 00:19:41 +00002034 ocsp_der = None
2035 ocsp_state = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002036
mattm@chromium.org07e28412012-09-05 00:19:41 +00002037 if self.options.ocsp == 'ok':
2038 ocsp_state = minica.OCSP_STATE_GOOD
2039 elif self.options.ocsp == 'revoked':
2040 ocsp_state = minica.OCSP_STATE_REVOKED
2041 elif self.options.ocsp == 'invalid':
2042 ocsp_state = minica.OCSP_STATE_INVALID
2043 elif self.options.ocsp == 'unauthorized':
2044 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2045 elif self.options.ocsp == 'unknown':
2046 ocsp_state = minica.OCSP_STATE_UNKNOWN
2047 else:
2048 raise testserver_base.OptionError('unknown OCSP status: ' +
2049 self.options.ocsp_status)
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002050
mattm@chromium.org07e28412012-09-05 00:19:41 +00002051 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2052 subject = "127.0.0.1",
2053 ocsp_url = ("http://%s:%d/ocsp" %
2054 (host, self.__ocsp_server.server_port)),
2055 ocsp_state = ocsp_state)
2056
2057 self.__ocsp_server.ocsp_response = ocsp_der
2058
2059 for ca_cert in self.options.ssl_client_ca:
2060 if not os.path.isfile(ca_cert):
2061 raise testserver_base.OptionError(
2062 'specified trusted client CA file not found: ' + ca_cert +
2063 ' exiting...')
2064 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2065 self.options.ssl_client_auth,
2066 self.options.ssl_client_ca,
2067 self.options.ssl_bulk_cipher,
2068 self.options.record_resume,
2069 self.options.tls_intolerant)
2070 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2071 else:
2072 server = HTTPServer((host, port), TestPageHandler)
2073 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2074
2075 server.data_dir = self.__make_data_dir()
2076 server.file_root_url = self.options.file_root_url
2077 server_data['port'] = server.server_port
2078 server._device_management_handler = None
2079 server.policy_keys = self.options.policy_keys
2080 server.policy_user = self.options.policy_user
2081 server.gdata_auth_token = self.options.auth_token
2082 elif self.options.server_type == SERVER_SYNC:
2083 xmpp_port = self.options.xmpp_port
2084 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2085 print 'Sync HTTP server started on port %d...' % server.server_port
2086 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2087 server_data['port'] = server.server_port
2088 server_data['xmpp_port'] = server.xmpp_port
2089 elif self.options.server_type == SERVER_TCP_ECHO:
2090 # Used for generating the key (randomly) that encodes the "echo request"
2091 # message.
2092 random.seed()
2093 server = TCPEchoServer((host, port), TCPEchoHandler)
2094 print 'Echo TCP server started on port %d...' % server.server_port
2095 server_data['port'] = server.server_port
2096 elif self.options.server_type == SERVER_UDP_ECHO:
2097 # Used for generating the key (randomly) that encodes the "echo request"
2098 # message.
2099 random.seed()
2100 server = UDPEchoServer((host, port), UDPEchoHandler)
2101 print 'Echo UDP server started on port %d...' % server.server_port
2102 server_data['port'] = server.server_port
2103 elif self.options.server_type == SERVER_FTP:
2104 my_data_dir = self.__make_data_dir()
2105
2106 # Instantiate a dummy authorizer for managing 'virtual' users
2107 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2108
2109 # Define a new user having full r/w permissions and a read-only
2110 # anonymous user
2111 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2112
2113 authorizer.add_anonymous(my_data_dir)
2114
2115 # Instantiate FTP handler class
2116 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2117 ftp_handler.authorizer = authorizer
2118
2119 # Define a customized banner (string returned when client connects)
2120 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2121 pyftpdlib.ftpserver.__ver__)
2122
2123 # Instantiate FTP server class and listen to address:port
2124 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2125 server_data['port'] = server.socket.getsockname()[1]
2126 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002127 else:
mattm@chromium.org07e28412012-09-05 00:19:41 +00002128 raise testserver_base.OptionError('unknown server type' +
2129 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002130
mattm@chromium.org07e28412012-09-05 00:19:41 +00002131 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002132
mattm@chromium.org07e28412012-09-05 00:19:41 +00002133 def run_server(self):
2134 if self.__ocsp_server:
2135 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002136
mattm@chromium.org07e28412012-09-05 00:19:41 +00002137 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002138
mattm@chromium.org07e28412012-09-05 00:19:41 +00002139 if self.__ocsp_server:
2140 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002141
mattm@chromium.org07e28412012-09-05 00:19:41 +00002142 def add_options(self):
2143 testserver_base.TestServerRunner.add_options(self)
2144 self.option_parser.add_option('-f', '--ftp', action='store_const',
2145 const=SERVER_FTP, default=SERVER_HTTP,
2146 dest='server_type',
2147 help='start up an FTP server.')
2148 self.option_parser.add_option('--sync', action='store_const',
2149 const=SERVER_SYNC, default=SERVER_HTTP,
2150 dest='server_type',
2151 help='start up a sync server.')
2152 self.option_parser.add_option('--tcp-echo', action='store_const',
2153 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2154 dest='server_type',
2155 help='start up a tcp echo server.')
2156 self.option_parser.add_option('--udp-echo', action='store_const',
2157 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2158 dest='server_type',
2159 help='start up a udp echo server.')
2160 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2161 help='Port used by the XMPP server. If '
2162 'unspecified, the XMPP server will listen on '
2163 'an ephemeral port.')
2164 self.option_parser.add_option('--data-dir', dest='data_dir',
2165 help='Directory from which to read the '
2166 'files.')
2167 self.option_parser.add_option('--https', action='store_true',
2168 dest='https', help='Specify that https '
2169 'should be used.')
2170 self.option_parser.add_option('--cert-and-key-file',
2171 dest='cert_and_key_file', help='specify the '
2172 'path to the file containing the certificate '
2173 'and private key for the server in PEM '
2174 'format')
2175 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2176 help='The type of OCSP response generated '
2177 'for the automatically generated '
2178 'certificate. One of [ok,revoked,invalid]')
2179 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2180 default='0', type='int',
2181 help='If nonzero, certain TLS connections '
2182 'will be aborted in order to test version '
2183 'fallback. 1 means all TLS versions will be '
2184 'aborted. 2 means TLS 1.1 or higher will be '
2185 'aborted. 3 means TLS 1.2 or higher will be '
2186 'aborted.')
2187 self.option_parser.add_option('--https-record-resume',
2188 dest='record_resume', const=True,
2189 default=False, action='store_const',
2190 help='Record resumption cache events rather '
2191 'than resuming as normal. Allows the use of '
2192 'the /ssl-session-cache request')
2193 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2194 help='Require SSL client auth on every '
2195 'connection.')
2196 self.option_parser.add_option('--ssl-client-ca', action='append',
2197 default=[], help='Specify that the client '
2198 'certificate request should include the CA '
2199 'named in the subject of the DER-encoded '
2200 'certificate contained in the specified '
2201 'file. This option may appear multiple '
2202 'times, indicating multiple CA names should '
2203 'be sent in the request.')
2204 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2205 help='Specify the bulk encryption '
2206 'algorithm(s) that will be accepted by the '
2207 'SSL server. Valid values are "aes256", '
2208 '"aes128", "3des", "rc4". If omitted, all '
2209 'algorithms will be used. This option may '
2210 'appear multiple times, indicating '
2211 'multiple algorithms should be enabled.');
2212 self.option_parser.add_option('--file-root-url', default='/files/',
2213 help='Specify a root URL for files served.')
2214 self.option_parser.add_option('--policy-key', action='append',
2215 dest='policy_keys',
2216 help='Specify a path to a PEM-encoded '
2217 'private key to use for policy signing. May '
2218 'be specified multiple times in order to '
2219 'load multipe keys into the server. If the '
2220 'server has multiple keys, it will rotate '
2221 'through them in at each request a '
2222 'round-robin fashion. The server will '
2223 'generate a random key if none is specified '
2224 'on the command line.')
2225 self.option_parser.add_option('--policy-user',
2226 default='user@example.com',
2227 dest='policy_user',
2228 help='Specify the user name the server '
2229 'should report back to the client as the '
2230 'user owning the token used for making the '
2231 'policy request.')
2232 self.option_parser.add_option('--auth-token', dest='auth_token',
2233 help='Specify the auth token which should be '
2234 'used in the authorization header for GData.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002235
initial.commit94958cf2008-07-26 22:42:52 +00002236
2237if __name__ == '__main__':
mattm@chromium.org07e28412012-09-05 00:19:41 +00002238 sys.exit(ServerRunner().main())