blob: 8bded89dbcec786f6ed08b0f75cb9cd86a0f7ec2 [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.org11f17fb2012-09-23 00:06:27 +000021import hashlib
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000022import json
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000023import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000024import minica
initial.commit94958cf2008-07-26 22:42:52 +000025import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000026import random
initial.commit94958cf2008-07-26 22:42:52 +000027import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000028import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000029import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000030import SocketServer
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000036import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000037
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000038import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000039import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000040import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000041import tlslite
42import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043
mattm@chromium.org830a3712012-11-07 23:00:07 +000044BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000045sys.path.insert(
46 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
47from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000048
maruel@chromium.org756cf982009-03-05 12:46:38 +000049SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000050SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000051SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000052SERVER_TCP_ECHO = 3
53SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000054SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000055SERVER_WEBSOCKET = 6
56
57# Default request queue size for WebSocketServer.
58_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000059
mattm@chromium.org830a3712012-11-07 23:00:07 +000060
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000061# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000062debug_output = sys.stderr
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +000063def debug(string):
64 debug_output.write(string + "\n")
initial.commit94958cf2008-07-26 22:42:52 +000065 debug_output.flush()
66
mattm@chromium.org830a3712012-11-07 23:00:07 +000067
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000068class WebSocketOptions:
69 """Holds options for WebSocketServer."""
70
71 def __init__(self, host, port, data_dir):
72 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
73 self.server_host = host
74 self.port = port
75 self.websock_handlers = data_dir
76 self.scan_dir = None
77 self.allow_handlers_outside_root_dir = False
78 self.websock_handlers_map_file = None
79 self.cgi_directories = []
80 self.is_executable_method = None
81 self.allow_draft75 = False
82 self.strict = True
83
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000084 self.use_tls = False
85 self.private_key = None
86 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000087 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000088 self.tls_client_ca = None
89 self.use_basic_auth = False
90
mattm@chromium.org830a3712012-11-07 23:00:07 +000091
agl@chromium.orgf9e66792011-12-12 22:22:19 +000092class RecordingSSLSessionCache(object):
93 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
94 lookups and inserts in order to test session cache behaviours."""
95
96 def __init__(self):
97 self.log = []
98
99 def __getitem__(self, sessionID):
100 self.log.append(('lookup', sessionID))
101 raise KeyError()
102
103 def __setitem__(self, sessionID, session):
104 self.log.append(('insert', sessionID))
105
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000106
107class ClientRestrictingServerMixIn:
108 """Implements verify_request to limit connections to our configured IP
109 address."""
110
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000111 def verify_request(self, _request, client_address):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000112 return client_address[0] == self.server_address[0]
113
114
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000115class BrokenPipeHandlerMixIn:
116 """Allows the server to deal with "broken pipe" errors (which happen if the
117 browser quits with outstanding requests, like for the favicon). This mix-in
118 requires the class to derive from SocketServer.BaseServer and not override its
119 handle_error() method. """
120
121 def handle_error(self, request, client_address):
122 value = sys.exc_info()[1]
123 if isinstance(value, socket.error):
124 err = value.args[0]
125 if sys.platform in ('win32', 'cygwin'):
126 # "An established connection was aborted by the software in your host."
127 pipe_err = 10053
128 else:
129 pipe_err = errno.EPIPE
130 if err == pipe_err:
131 print "testserver.py: Broken pipe"
132 return
133 SocketServer.BaseServer.handle_error(self, request, client_address)
134
135
initial.commit94958cf2008-07-26 22:42:52 +0000136class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000137 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000138 to be exited cleanly (by setting its "stop" member to True)."""
139
140 def serve_forever(self):
141 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000142 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000143 while not self.stop:
144 self.handle_request()
145 self.socket.close()
146
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000147
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000148class HTTPServer(ClientRestrictingServerMixIn,
149 BrokenPipeHandlerMixIn,
150 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000151 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000152 verification."""
153
154 pass
155
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000156class OCSPServer(ClientRestrictingServerMixIn,
157 BrokenPipeHandlerMixIn,
158 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000159 """This is a specialization of HTTPServer that serves an
160 OCSP response"""
161
162 def serve_forever_on_thread(self):
163 self.thread = threading.Thread(target = self.serve_forever,
164 name = "OCSPServerThread")
165 self.thread.start()
166
167 def stop_serving(self):
168 self.shutdown()
169 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000170
mattm@chromium.org830a3712012-11-07 23:00:07 +0000171
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000172class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
173 ClientRestrictingServerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000174 BrokenPipeHandlerMixIn,
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000175 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000176 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000177 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000178
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000179 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000180 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000181 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000182 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
183 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000184 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000185 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000186 self.tls_intolerant = tls_intolerant
187
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000188 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000189 s = open(ca_file).read()
190 x509 = tlslite.api.X509()
191 x509.parse(s)
192 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000193 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
194 if ssl_bulk_ciphers is not None:
195 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000196
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000197 if record_resume_info:
198 # If record_resume_info is true then we'll replace the session cache with
199 # an object that records the lookups and inserts that it sees.
200 self.session_cache = RecordingSSLSessionCache()
201 else:
202 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000203 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
204
205 def handshake(self, tlsConnection):
206 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000207
initial.commit94958cf2008-07-26 22:42:52 +0000208 try:
209 tlsConnection.handshakeServer(certChain=self.cert_chain,
210 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000211 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000212 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000213 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000214 reqCAs=self.ssl_client_cas,
215 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000216 tlsConnection.ignoreAbruptClose = True
217 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000218 except tlslite.api.TLSAbruptCloseError:
219 # Ignore abrupt close.
220 return True
initial.commit94958cf2008-07-26 22:42:52 +0000221 except tlslite.api.TLSError, error:
222 print "Handshake failure:", str(error)
223 return False
224
akalin@chromium.org154bb132010-11-12 02:20:27 +0000225
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000226class SyncHTTPServer(ClientRestrictingServerMixIn,
227 BrokenPipeHandlerMixIn,
228 StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000229 """An HTTP server that handles sync commands."""
230
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000231 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000232 # We import here to avoid pulling in chromiumsync's dependencies
233 # unless strictly necessary.
234 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000235 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000236 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000237 self._sync_handler = chromiumsync.TestServer()
238 self._xmpp_socket_map = {}
239 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000240 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000241 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000242 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000243
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000244 def GetXmppServer(self):
245 return self._xmpp_server
246
akalin@chromium.org154bb132010-11-12 02:20:27 +0000247 def HandleCommand(self, query, raw_request):
248 return self._sync_handler.HandleCommand(query, raw_request)
249
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000250 def HandleRequestNoBlock(self):
251 """Handles a single request.
252
253 Copied from SocketServer._handle_request_noblock().
254 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000255
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000256 try:
257 request, client_address = self.get_request()
258 except socket.error:
259 return
260 if self.verify_request(request, client_address):
261 try:
262 self.process_request(request, client_address)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000263 except Exception:
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000264 self.handle_error(request, client_address)
265 self.close_request(request)
266
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000267 def SetAuthenticated(self, auth_valid):
268 self.authenticated = auth_valid
269
270 def GetAuthenticated(self):
271 return self.authenticated
272
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000273 def serve_forever(self):
274 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
275 """
276
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000277 def HandleXmppSocket(fd, socket_map, handler):
278 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000279
280 Adapted from asyncore.read() et al.
281 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000282
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000283 xmpp_connection = socket_map.get(fd)
284 # This could happen if a previous handler call caused fd to get
285 # removed from socket_map.
286 if xmpp_connection is None:
287 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000288 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000289 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000290 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
291 raise
292 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000293 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000294
295 while True:
296 read_fds = [ self.fileno() ]
297 write_fds = []
298 exceptional_fds = []
299
300 for fd, xmpp_connection in self._xmpp_socket_map.items():
301 is_r = xmpp_connection.readable()
302 is_w = xmpp_connection.writable()
303 if is_r:
304 read_fds.append(fd)
305 if is_w:
306 write_fds.append(fd)
307 if is_r or is_w:
308 exceptional_fds.append(fd)
309
310 try:
311 read_fds, write_fds, exceptional_fds = (
312 select.select(read_fds, write_fds, exceptional_fds))
313 except select.error, err:
314 if err.args[0] != errno.EINTR:
315 raise
316 else:
317 continue
318
319 for fd in read_fds:
320 if fd == self.fileno():
321 self.HandleRequestNoBlock()
322 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000323 HandleXmppSocket(fd, self._xmpp_socket_map,
324 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000325
326 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000327 HandleXmppSocket(fd, self._xmpp_socket_map,
328 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000329
330 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000331 HandleXmppSocket(fd, self._xmpp_socket_map,
332 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000333
akalin@chromium.org154bb132010-11-12 02:20:27 +0000334
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000335class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
336 """This is a specialization of FTPServer that adds client verification."""
337
338 pass
339
340
341class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000342 """A TCP echo server that echoes back what it has received."""
343
344 def server_bind(self):
345 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000346
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000347 SocketServer.TCPServer.server_bind(self)
348 host, port = self.socket.getsockname()[:2]
349 self.server_name = socket.getfqdn(host)
350 self.server_port = port
351
352 def serve_forever(self):
353 self.stop = False
354 self.nonce_time = None
355 while not self.stop:
356 self.handle_request()
357 self.socket.close()
358
359
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000360class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000361 """A UDP echo server that echoes back what it has received."""
362
363 def server_bind(self):
364 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000365
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000366 SocketServer.UDPServer.server_bind(self)
367 host, port = self.socket.getsockname()[:2]
368 self.server_name = socket.getfqdn(host)
369 self.server_port = port
370
371 def serve_forever(self):
372 self.stop = False
373 self.nonce_time = None
374 while not self.stop:
375 self.handle_request()
376 self.socket.close()
377
378
akalin@chromium.org154bb132010-11-12 02:20:27 +0000379class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
380
381 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000382 connect_handlers, get_handlers, head_handlers, post_handlers,
383 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000384 self._connect_handlers = connect_handlers
385 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000386 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000387 self._post_handlers = post_handlers
388 self._put_handlers = put_handlers
389 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
390 self, request, client_address, socket_server)
391
392 def log_request(self, *args, **kwargs):
393 # Disable request logging to declutter test log output.
394 pass
395
396 def _ShouldHandleRequest(self, handler_name):
397 """Determines if the path can be handled by the handler.
398
399 We consider a handler valid if the path begins with the
400 handler name. It can optionally be followed by "?*", "/*".
401 """
402
403 pattern = re.compile('%s($|\?|/).*' % handler_name)
404 return pattern.match(self.path)
405
406 def do_CONNECT(self):
407 for handler in self._connect_handlers:
408 if handler():
409 return
410
411 def do_GET(self):
412 for handler in self._get_handlers:
413 if handler():
414 return
415
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000416 def do_HEAD(self):
417 for handler in self._head_handlers:
418 if handler():
419 return
420
akalin@chromium.org154bb132010-11-12 02:20:27 +0000421 def do_POST(self):
422 for handler in self._post_handlers:
423 if handler():
424 return
425
426 def do_PUT(self):
427 for handler in self._put_handlers:
428 if handler():
429 return
430
431
432class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000433
434 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000435 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000436 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000437 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000438 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000439 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.NoCacheMaxAgeTimeHandler,
441 self.NoCacheTimeHandler,
442 self.CacheTimeHandler,
443 self.CacheExpiresHandler,
444 self.CacheProxyRevalidateHandler,
445 self.CachePrivateHandler,
446 self.CachePublicHandler,
447 self.CacheSMaxAgeHandler,
448 self.CacheMustRevalidateHandler,
449 self.CacheMustRevalidateMaxAgeHandler,
450 self.CacheNoStoreHandler,
451 self.CacheNoStoreMaxAgeHandler,
452 self.CacheNoTransformHandler,
453 self.DownloadHandler,
454 self.DownloadFinishHandler,
455 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000456 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000457 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000458 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000459 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000460 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000461 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000462 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000463 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000464 self.AuthBasicHandler,
465 self.AuthDigestHandler,
466 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000467 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000468 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000469 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000470 self.ServerRedirectHandler,
471 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000472 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000473 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000474 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000475 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000476 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000477 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000478 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000479 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000480 self.DeviceManagementHandler,
481 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000482 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000483 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000484 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000485 head_handlers = [
486 self.FileHandler,
487 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000488
maruel@google.come250a9b2009-03-10 17:39:46 +0000489 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000490 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000491 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000492 'gif': 'image/gif',
493 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000494 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000495 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000496 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000497 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000498 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000499 }
initial.commit94958cf2008-07-26 22:42:52 +0000500 self._default_mime_type = 'text/html'
501
akalin@chromium.org154bb132010-11-12 02:20:27 +0000502 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000503 connect_handlers, get_handlers, head_handlers,
504 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000505
initial.commit94958cf2008-07-26 22:42:52 +0000506 def GetMIMETypeFromName(self, file_name):
507 """Returns the mime type for the specified file_name. So far it only looks
508 at the file extension."""
509
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000510 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000511 if len(extension) == 0:
512 # no extension.
513 return self._default_mime_type
514
ericroman@google.comc17ca532009-05-07 03:51:05 +0000515 # extension starts with a dot, so we need to remove it
516 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000517
initial.commit94958cf2008-07-26 22:42:52 +0000518 def NoCacheMaxAgeTimeHandler(self):
519 """This request handler yields a page with the title set to the current
520 system time, and no caching requested."""
521
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000522 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000523 return False
524
525 self.send_response(200)
526 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000527 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000528 self.end_headers()
529
maruel@google.come250a9b2009-03-10 17:39:46 +0000530 self.wfile.write('<html><head><title>%s</title></head></html>' %
531 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000532
533 return True
534
535 def NoCacheTimeHandler(self):
536 """This request handler yields a page with the title set to the current
537 system time, and no caching requested."""
538
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000539 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000540 return False
541
542 self.send_response(200)
543 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000544 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000545 self.end_headers()
546
maruel@google.come250a9b2009-03-10 17:39:46 +0000547 self.wfile.write('<html><head><title>%s</title></head></html>' %
548 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000549
550 return True
551
552 def CacheTimeHandler(self):
553 """This request handler yields a page with the title set to the current
554 system time, and allows caching for one minute."""
555
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000556 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 self.send_response(200)
560 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000561 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000562 self.end_headers()
563
maruel@google.come250a9b2009-03-10 17:39:46 +0000564 self.wfile.write('<html><head><title>%s</title></head></html>' %
565 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000566
567 return True
568
569 def CacheExpiresHandler(self):
570 """This request handler yields a page with the title set to the current
571 system time, and set the page to expire on 1 Jan 2099."""
572
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000573 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000574 return False
575
576 self.send_response(200)
577 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000578 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000579 self.end_headers()
580
maruel@google.come250a9b2009-03-10 17:39:46 +0000581 self.wfile.write('<html><head><title>%s</title></head></html>' %
582 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000583
584 return True
585
586 def CacheProxyRevalidateHandler(self):
587 """This request handler yields a page with the title set to the current
588 system time, and allows caching for 60 seconds"""
589
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000590 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000591 return False
592
593 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000594 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000595 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
596 self.end_headers()
597
maruel@google.come250a9b2009-03-10 17:39:46 +0000598 self.wfile.write('<html><head><title>%s</title></head></html>' %
599 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000600
601 return True
602
603 def CachePrivateHandler(self):
604 """This request handler yields a page with the title set to the current
605 system time, and allows caching for 5 seconds."""
606
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000607 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000608 return False
609
610 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000611 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000612 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000613 self.end_headers()
614
maruel@google.come250a9b2009-03-10 17:39:46 +0000615 self.wfile.write('<html><head><title>%s</title></head></html>' %
616 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000617
618 return True
619
620 def CachePublicHandler(self):
621 """This request handler yields a page with the title set to the current
622 system time, and allows caching for 5 seconds."""
623
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000624 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000625 return False
626
627 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000628 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000629 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000630 self.end_headers()
631
maruel@google.come250a9b2009-03-10 17:39:46 +0000632 self.wfile.write('<html><head><title>%s</title></head></html>' %
633 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000634
635 return True
636
637 def CacheSMaxAgeHandler(self):
638 """This request handler yields a page with the title set to the current
639 system time, and does not allow for caching."""
640
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000641 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000642 return False
643
644 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000645 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000646 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
647 self.end_headers()
648
maruel@google.come250a9b2009-03-10 17:39:46 +0000649 self.wfile.write('<html><head><title>%s</title></head></html>' %
650 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000651
652 return True
653
654 def CacheMustRevalidateHandler(self):
655 """This request handler yields a page with the title set to the current
656 system time, and does not allow caching."""
657
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000658 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000659 return False
660
661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000662 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000663 self.send_header('Cache-Control', 'must-revalidate')
664 self.end_headers()
665
maruel@google.come250a9b2009-03-10 17:39:46 +0000666 self.wfile.write('<html><head><title>%s</title></head></html>' %
667 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000668
669 return True
670
671 def CacheMustRevalidateMaxAgeHandler(self):
672 """This request handler yields a page with the title set to the current
673 system time, and does not allow caching event though max-age of 60
674 seconds is specified."""
675
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000676 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000677 return False
678
679 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000680 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000681 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
682 self.end_headers()
683
maruel@google.come250a9b2009-03-10 17:39:46 +0000684 self.wfile.write('<html><head><title>%s</title></head></html>' %
685 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000686
687 return True
688
initial.commit94958cf2008-07-26 22:42:52 +0000689 def CacheNoStoreHandler(self):
690 """This request handler yields a page with the title set to the current
691 system time, and does not allow the page to be stored."""
692
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000693 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000694 return False
695
696 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000697 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000698 self.send_header('Cache-Control', 'no-store')
699 self.end_headers()
700
maruel@google.come250a9b2009-03-10 17:39:46 +0000701 self.wfile.write('<html><head><title>%s</title></head></html>' %
702 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000703
704 return True
705
706 def CacheNoStoreMaxAgeHandler(self):
707 """This request handler yields a page with the title set to the current
708 system time, and does not allow the page to be stored even though max-age
709 of 60 seconds is specified."""
710
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000711 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000712 return False
713
714 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000715 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000716 self.send_header('Cache-Control', 'max-age=60, no-store')
717 self.end_headers()
718
maruel@google.come250a9b2009-03-10 17:39:46 +0000719 self.wfile.write('<html><head><title>%s</title></head></html>' %
720 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000721
722 return True
723
724
725 def CacheNoTransformHandler(self):
726 """This request handler yields a page with the title set to the current
727 system time, and does not allow the content to transformed during
728 user-agent caching"""
729
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000730 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000731 return False
732
733 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000734 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000735 self.send_header('Cache-Control', 'no-transform')
736 self.end_headers()
737
maruel@google.come250a9b2009-03-10 17:39:46 +0000738 self.wfile.write('<html><head><title>%s</title></head></html>' %
739 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000740
741 return True
742
743 def EchoHeader(self):
744 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000745
ananta@chromium.org219b2062009-10-23 16:09:41 +0000746 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000747
ananta@chromium.org56812d02011-04-07 17:52:05 +0000748 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000749 """This function echoes back the value of a specific request header while
750 allowing caching for 16 hours."""
751
ananta@chromium.org56812d02011-04-07 17:52:05 +0000752 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000753
754 def EchoHeaderHelper(self, echo_header):
755 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000756
ananta@chromium.org219b2062009-10-23 16:09:41 +0000757 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000758 return False
759
760 query_char = self.path.find('?')
761 if query_char != -1:
762 header_name = self.path[query_char+1:]
763
764 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000765 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000766 if echo_header == '/echoheadercache':
767 self.send_header('Cache-control', 'max-age=60000')
768 else:
769 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000770 # insert a vary header to properly indicate that the cachability of this
771 # request is subject to value of the request header being echoed.
772 if len(header_name) > 0:
773 self.send_header('Vary', header_name)
774 self.end_headers()
775
776 if len(header_name) > 0:
777 self.wfile.write(self.headers.getheader(header_name))
778
779 return True
780
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000781 def ReadRequestBody(self):
782 """This function reads the body of the current HTTP request, handling
783 both plain and chunked transfer encoded requests."""
784
785 if self.headers.getheader('transfer-encoding') != 'chunked':
786 length = int(self.headers.getheader('content-length'))
787 return self.rfile.read(length)
788
789 # Read the request body as chunks.
790 body = ""
791 while True:
792 line = self.rfile.readline()
793 length = int(line, 16)
794 if length == 0:
795 self.rfile.readline()
796 break
797 body += self.rfile.read(length)
798 self.rfile.read(2)
799 return body
800
initial.commit94958cf2008-07-26 22:42:52 +0000801 def EchoHandler(self):
802 """This handler just echoes back the payload of the request, for testing
803 form submission."""
804
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000805 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000806 return False
807
808 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000809 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000811 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000812 return True
813
814 def EchoTitleHandler(self):
815 """This handler is like Echo, but sets the page title to the request."""
816
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000817 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000818 return False
819
820 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000821 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000822 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000823 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000824 self.wfile.write('<html><head><title>')
825 self.wfile.write(request)
826 self.wfile.write('</title></head></html>')
827 return True
828
829 def EchoAllHandler(self):
830 """This handler yields a (more) human-readable page listing information
831 about the request header & contents."""
832
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000833 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000834 return False
835
836 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000837 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000838 self.end_headers()
839 self.wfile.write('<html><head><style>'
840 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
841 '</style></head><body>'
842 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000843 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000844 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000845
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000846 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000847 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000848 params = cgi.parse_qs(qs, keep_blank_values=1)
849
850 for param in params:
851 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000852
853 self.wfile.write('</pre>')
854
855 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
856
857 self.wfile.write('</body></html>')
858 return True
859
860 def DownloadHandler(self):
861 """This handler sends a downloadable file with or without reporting
862 the size (6K)."""
863
864 if self.path.startswith("/download-unknown-size"):
865 send_length = False
866 elif self.path.startswith("/download-known-size"):
867 send_length = True
868 else:
869 return False
870
871 #
872 # The test which uses this functionality is attempting to send
873 # small chunks of data to the client. Use a fairly large buffer
874 # so that we'll fill chrome's IO buffer enough to force it to
875 # actually write the data.
876 # See also the comments in the client-side of this test in
877 # download_uitest.cc
878 #
879 size_chunk1 = 35*1024
880 size_chunk2 = 10*1024
881
882 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000883 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000884 self.send_header('Cache-Control', 'max-age=0')
885 if send_length:
886 self.send_header('Content-Length', size_chunk1 + size_chunk2)
887 self.end_headers()
888
889 # First chunk of data:
890 self.wfile.write("*" * size_chunk1)
891 self.wfile.flush()
892
893 # handle requests until one of them clears this flag.
894 self.server.waitForDownload = True
895 while self.server.waitForDownload:
896 self.server.handle_request()
897
898 # Second chunk of data:
899 self.wfile.write("*" * size_chunk2)
900 return True
901
902 def DownloadFinishHandler(self):
903 """This handler just tells the server to finish the current download."""
904
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000905 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000906 return False
907
908 self.server.waitForDownload = False
909 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000910 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000911 self.send_header('Cache-Control', 'max-age=0')
912 self.end_headers()
913 return True
914
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000915 def _ReplaceFileData(self, data, query_parameters):
916 """Replaces matching substrings in a file.
917
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000918 If the 'replace_text' URL query parameter is present, it is expected to be
919 of the form old_text:new_text, which indicates that any old_text strings in
920 the file are replaced with new_text. Multiple 'replace_text' parameters may
921 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000922
923 If the parameters are not present, |data| is returned.
924 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000925
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000926 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000927 replace_text_values = query_dict.get('replace_text', [])
928 for replace_text_value in replace_text_values:
929 replace_text_args = replace_text_value.split(':')
930 if len(replace_text_args) != 2:
931 raise ValueError(
932 'replace_text must be of form old_text:new_text. Actual value: %s' %
933 replace_text_value)
934 old_text_b64, new_text_b64 = replace_text_args
935 old_text = base64.urlsafe_b64decode(old_text_b64)
936 new_text = base64.urlsafe_b64decode(new_text_b64)
937 data = data.replace(old_text, new_text)
938 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000939
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000940 def ZipFileHandler(self):
941 """This handler sends the contents of the requested file in compressed form.
942 Can pass in a parameter that specifies that the content length be
943 C - the compressed size (OK),
944 U - the uncompressed size (Non-standard, but handled),
945 S - less than compressed (OK because we keep going),
946 M - larger than compressed but less than uncompressed (an error),
947 L - larger than uncompressed (an error)
948 Example: compressedfiles/Picture_1.doc?C
949 """
950
951 prefix = "/compressedfiles/"
952 if not self.path.startswith(prefix):
953 return False
954
955 # Consume a request body if present.
956 if self.command == 'POST' or self.command == 'PUT' :
957 self.ReadRequestBody()
958
959 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
960
961 if not query in ('C', 'U', 'S', 'M', 'L'):
962 return False
963
964 sub_path = url_path[len(prefix):]
965 entries = sub_path.split('/')
966 file_path = os.path.join(self.server.data_dir, *entries)
967 if os.path.isdir(file_path):
968 file_path = os.path.join(file_path, 'index.html')
969
970 if not os.path.isfile(file_path):
971 print "File not found " + sub_path + " full path:" + file_path
972 self.send_error(404)
973 return True
974
975 f = open(file_path, "rb")
976 data = f.read()
977 uncompressed_len = len(data)
978 f.close()
979
980 # Compress the data.
981 data = zlib.compress(data)
982 compressed_len = len(data)
983
984 content_length = compressed_len
985 if query == 'U':
986 content_length = uncompressed_len
987 elif query == 'S':
988 content_length = compressed_len / 2
989 elif query == 'M':
990 content_length = (compressed_len + uncompressed_len) / 2
991 elif query == 'L':
992 content_length = compressed_len + uncompressed_len
993
994 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000995 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000996 self.send_header('Content-encoding', 'deflate')
997 self.send_header('Connection', 'close')
998 self.send_header('Content-Length', content_length)
999 self.send_header('ETag', '\'' + file_path + '\'')
1000 self.end_headers()
1001
1002 self.wfile.write(data)
1003
1004 return True
1005
initial.commit94958cf2008-07-26 22:42:52 +00001006 def FileHandler(self):
1007 """This handler sends the contents of the requested file. Wow, it's like
1008 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001009
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001010 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +00001011 if not self.path.startswith(prefix):
1012 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001013 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +00001014
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001015 def PostOnlyFileHandler(self):
1016 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001017
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +00001018 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001019 if not self.path.startswith(prefix):
1020 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001021 return self._FileHandlerHelper(prefix)
1022
1023 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001024 request_body = ''
1025 if self.command == 'POST' or self.command == 'PUT':
1026 # Consume a request body if present.
1027 request_body = self.ReadRequestBody()
1028
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001029 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001030 query_dict = cgi.parse_qs(query)
1031
1032 expected_body = query_dict.get('expected_body', [])
1033 if expected_body and request_body not in expected_body:
1034 self.send_response(404)
1035 self.end_headers()
1036 self.wfile.write('')
1037 return True
1038
1039 expected_headers = query_dict.get('expected_headers', [])
1040 for expected_header in expected_headers:
1041 header_name, expected_value = expected_header.split(':')
1042 if self.headers.getheader(header_name) != expected_value:
1043 self.send_response(404)
1044 self.end_headers()
1045 self.wfile.write('')
1046 return True
1047
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001048 sub_path = url_path[len(prefix):]
1049 entries = sub_path.split('/')
1050 file_path = os.path.join(self.server.data_dir, *entries)
1051 if os.path.isdir(file_path):
1052 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001053
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001054 if not os.path.isfile(file_path):
1055 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001056 self.send_error(404)
1057 return True
1058
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001059 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001060 data = f.read()
1061 f.close()
1062
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001063 data = self._ReplaceFileData(data, query)
1064
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001065 old_protocol_version = self.protocol_version
1066
initial.commit94958cf2008-07-26 22:42:52 +00001067 # If file.mock-http-headers exists, it contains the headers we
1068 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001069 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001070 if os.path.isfile(headers_path):
1071 f = open(headers_path, "r")
1072
1073 # "HTTP/1.1 200 OK"
1074 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001075 http_major, http_minor, status_code = re.findall(
1076 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1077 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001078 self.send_response(int(status_code))
1079
1080 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001081 header_values = re.findall('(\S+):\s*(.*)', line)
1082 if len(header_values) > 0:
1083 # "name: value"
1084 name, value = header_values[0]
1085 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001086 f.close()
1087 else:
1088 # Could be more generic once we support mime-type sniffing, but for
1089 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001090
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001091 range_header = self.headers.get('Range')
1092 if range_header and range_header.startswith('bytes='):
1093 # Note this doesn't handle all valid byte range_header values (i.e.
1094 # left open ended ones), just enough for what we needed so far.
1095 range_header = range_header[6:].split('-')
1096 start = int(range_header[0])
1097 if range_header[1]:
1098 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001099 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001100 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001101
1102 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001103 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1104 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001105 self.send_header('Content-Range', content_range)
1106 data = data[start: end + 1]
1107 else:
1108 self.send_response(200)
1109
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001110 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001111 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001112 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001113 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001114 self.end_headers()
1115
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001116 if (self.command != 'HEAD'):
1117 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001118
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001119 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001120 return True
1121
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001122 def SetCookieHandler(self):
1123 """This handler just sets a cookie, for testing cookie handling."""
1124
1125 if not self._ShouldHandleRequest("/set-cookie"):
1126 return False
1127
1128 query_char = self.path.find('?')
1129 if query_char != -1:
1130 cookie_values = self.path[query_char + 1:].split('&')
1131 else:
1132 cookie_values = ("",)
1133 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001134 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001135 for cookie_value in cookie_values:
1136 self.send_header('Set-Cookie', '%s' % cookie_value)
1137 self.end_headers()
1138 for cookie_value in cookie_values:
1139 self.wfile.write('%s' % cookie_value)
1140 return True
1141
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001142 def SetManyCookiesHandler(self):
1143 """This handler just sets a given number of cookies, for testing handling
1144 of large numbers of cookies."""
1145
1146 if not self._ShouldHandleRequest("/set-many-cookies"):
1147 return False
1148
1149 query_char = self.path.find('?')
1150 if query_char != -1:
1151 num_cookies = int(self.path[query_char + 1:])
1152 else:
1153 num_cookies = 0
1154 self.send_response(200)
1155 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001156 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001157 self.send_header('Set-Cookie', 'a=')
1158 self.end_headers()
1159 self.wfile.write('%d cookies were sent' % num_cookies)
1160 return True
1161
mattm@chromium.org983fc462012-06-30 00:52:08 +00001162 def ExpectAndSetCookieHandler(self):
1163 """Expects some cookies to be sent, and if they are, sets more cookies.
1164
1165 The expect parameter specifies a required cookie. May be specified multiple
1166 times.
1167 The set parameter specifies a cookie to set if all required cookies are
1168 preset. May be specified multiple times.
1169 The data parameter specifies the response body data to be returned."""
1170
1171 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1172 return False
1173
1174 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1175 query_dict = cgi.parse_qs(query)
1176 cookies = set()
1177 if 'Cookie' in self.headers:
1178 cookie_header = self.headers.getheader('Cookie')
1179 cookies.update([s.strip() for s in cookie_header.split(';')])
1180 got_all_expected_cookies = True
1181 for expected_cookie in query_dict.get('expect', []):
1182 if expected_cookie not in cookies:
1183 got_all_expected_cookies = False
1184 self.send_response(200)
1185 self.send_header('Content-Type', 'text/html')
1186 if got_all_expected_cookies:
1187 for cookie_value in query_dict.get('set', []):
1188 self.send_header('Set-Cookie', '%s' % cookie_value)
1189 self.end_headers()
1190 for data_value in query_dict.get('data', []):
1191 self.wfile.write(data_value)
1192 return True
1193
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001194 def SetHeaderHandler(self):
1195 """This handler sets a response header. Parameters are in the
1196 key%3A%20value&key2%3A%20value2 format."""
1197
1198 if not self._ShouldHandleRequest("/set-header"):
1199 return False
1200
1201 query_char = self.path.find('?')
1202 if query_char != -1:
1203 headers_values = self.path[query_char + 1:].split('&')
1204 else:
1205 headers_values = ("",)
1206 self.send_response(200)
1207 self.send_header('Content-Type', 'text/html')
1208 for header_value in headers_values:
1209 header_value = urllib.unquote(header_value)
1210 (key, value) = header_value.split(': ', 1)
1211 self.send_header(key, value)
1212 self.end_headers()
1213 for header_value in headers_values:
1214 self.wfile.write('%s' % header_value)
1215 return True
1216
initial.commit94958cf2008-07-26 22:42:52 +00001217 def AuthBasicHandler(self):
1218 """This handler tests 'Basic' authentication. It just sends a page with
1219 title 'user/pass' if you succeed."""
1220
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001221 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001222 return False
1223
1224 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001225 expected_password = 'secret'
1226 realm = 'testrealm'
1227 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001228
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001229 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1230 query_params = cgi.parse_qs(query, True)
1231 if 'set-cookie-if-challenged' in query_params:
1232 set_cookie_if_challenged = True
1233 if 'password' in query_params:
1234 expected_password = query_params['password'][0]
1235 if 'realm' in query_params:
1236 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001237
initial.commit94958cf2008-07-26 22:42:52 +00001238 auth = self.headers.getheader('authorization')
1239 try:
1240 if not auth:
1241 raise Exception('no auth')
1242 b64str = re.findall(r'Basic (\S+)', auth)[0]
1243 userpass = base64.b64decode(b64str)
1244 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001245 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001246 raise Exception('wrong password')
1247 except Exception, e:
1248 # Authentication failed.
1249 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001250 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001251 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001252 if set_cookie_if_challenged:
1253 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001254 self.end_headers()
1255 self.wfile.write('<html><head>')
1256 self.wfile.write('<title>Denied: %s</title>' % e)
1257 self.wfile.write('</head><body>')
1258 self.wfile.write('auth=%s<p>' % auth)
1259 self.wfile.write('b64str=%s<p>' % b64str)
1260 self.wfile.write('username: %s<p>' % username)
1261 self.wfile.write('userpass: %s<p>' % userpass)
1262 self.wfile.write('password: %s<p>' % password)
1263 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1264 self.wfile.write('</body></html>')
1265 return True
1266
1267 # Authentication successful. (Return a cachable response to allow for
1268 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001269 old_protocol_version = self.protocol_version
1270 self.protocol_version = "HTTP/1.1"
1271
initial.commit94958cf2008-07-26 22:42:52 +00001272 if_none_match = self.headers.getheader('if-none-match')
1273 if if_none_match == "abc":
1274 self.send_response(304)
1275 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001276 elif url_path.endswith(".gif"):
1277 # Using chrome/test/data/google/logo.gif as the test image
1278 test_image_path = ['google', 'logo.gif']
1279 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1280 if not os.path.isfile(gif_path):
1281 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001282 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001283 return True
1284
1285 f = open(gif_path, "rb")
1286 data = f.read()
1287 f.close()
1288
1289 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001290 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001291 self.send_header('Cache-control', 'max-age=60000')
1292 self.send_header('Etag', 'abc')
1293 self.end_headers()
1294 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001295 else:
1296 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001297 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001298 self.send_header('Cache-control', 'max-age=60000')
1299 self.send_header('Etag', 'abc')
1300 self.end_headers()
1301 self.wfile.write('<html><head>')
1302 self.wfile.write('<title>%s/%s</title>' % (username, password))
1303 self.wfile.write('</head><body>')
1304 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001305 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001306 self.wfile.write('</body></html>')
1307
rvargas@google.com54453b72011-05-19 01:11:11 +00001308 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001309 return True
1310
tonyg@chromium.org75054202010-03-31 22:06:10 +00001311 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001312 """Returns a nonce that's stable per request path for the server's lifetime.
1313 This is a fake implementation. A real implementation would only use a given
1314 nonce a single time (hence the name n-once). However, for the purposes of
1315 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001316
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001317 Args:
1318 force_reset: Iff set, the nonce will be changed. Useful for testing the
1319 "stale" response.
1320 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001321
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001322 if force_reset or not self.server.nonce_time:
1323 self.server.nonce_time = time.time()
1324 return hashlib.md5('privatekey%s%d' %
1325 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001326
1327 def AuthDigestHandler(self):
1328 """This handler tests 'Digest' authentication.
1329
1330 It just sends a page with title 'user/pass' if you succeed.
1331
1332 A stale response is sent iff "stale" is present in the request path.
1333 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001334
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001335 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001336 return False
1337
tonyg@chromium.org75054202010-03-31 22:06:10 +00001338 stale = 'stale' in self.path
1339 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001340 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001341 password = 'secret'
1342 realm = 'testrealm'
1343
1344 auth = self.headers.getheader('authorization')
1345 pairs = {}
1346 try:
1347 if not auth:
1348 raise Exception('no auth')
1349 if not auth.startswith('Digest'):
1350 raise Exception('not digest')
1351 # Pull out all the name="value" pairs as a dictionary.
1352 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1353
1354 # Make sure it's all valid.
1355 if pairs['nonce'] != nonce:
1356 raise Exception('wrong nonce')
1357 if pairs['opaque'] != opaque:
1358 raise Exception('wrong opaque')
1359
1360 # Check the 'response' value and make sure it matches our magic hash.
1361 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001362 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001363 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001364 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001365 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001366 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001367 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1368 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001369 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001370
1371 if pairs['response'] != response:
1372 raise Exception('wrong password')
1373 except Exception, e:
1374 # Authentication failed.
1375 self.send_response(401)
1376 hdr = ('Digest '
1377 'realm="%s", '
1378 'domain="/", '
1379 'qop="auth", '
1380 'algorithm=MD5, '
1381 'nonce="%s", '
1382 'opaque="%s"') % (realm, nonce, opaque)
1383 if stale:
1384 hdr += ', stale="TRUE"'
1385 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001386 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001387 self.end_headers()
1388 self.wfile.write('<html><head>')
1389 self.wfile.write('<title>Denied: %s</title>' % e)
1390 self.wfile.write('</head><body>')
1391 self.wfile.write('auth=%s<p>' % auth)
1392 self.wfile.write('pairs=%s<p>' % pairs)
1393 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1394 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1395 self.wfile.write('</body></html>')
1396 return True
1397
1398 # Authentication successful.
1399 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001400 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001401 self.end_headers()
1402 self.wfile.write('<html><head>')
1403 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1404 self.wfile.write('</head><body>')
1405 self.wfile.write('auth=%s<p>' % auth)
1406 self.wfile.write('pairs=%s<p>' % pairs)
1407 self.wfile.write('</body></html>')
1408
1409 return True
1410
1411 def SlowServerHandler(self):
1412 """Wait for the user suggested time before responding. The syntax is
1413 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001414
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001415 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001416 return False
1417 query_char = self.path.find('?')
1418 wait_sec = 1.0
1419 if query_char >= 0:
1420 try:
1421 wait_sec = int(self.path[query_char + 1:])
1422 except ValueError:
1423 pass
1424 time.sleep(wait_sec)
1425 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001426 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001427 self.end_headers()
1428 self.wfile.write("waited %d seconds" % wait_sec)
1429 return True
1430
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001431 def ChunkedServerHandler(self):
1432 """Send chunked response. Allows to specify chunks parameters:
1433 - waitBeforeHeaders - ms to wait before sending headers
1434 - waitBetweenChunks - ms to wait between chunks
1435 - chunkSize - size of each chunk in bytes
1436 - chunksNumber - number of chunks
1437 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1438 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001439
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001440 if not self._ShouldHandleRequest("/chunked"):
1441 return False
1442 query_char = self.path.find('?')
1443 chunkedSettings = {'waitBeforeHeaders' : 0,
1444 'waitBetweenChunks' : 0,
1445 'chunkSize' : 5,
1446 'chunksNumber' : 5}
1447 if query_char >= 0:
1448 params = self.path[query_char + 1:].split('&')
1449 for param in params:
1450 keyValue = param.split('=')
1451 if len(keyValue) == 2:
1452 try:
1453 chunkedSettings[keyValue[0]] = int(keyValue[1])
1454 except ValueError:
1455 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001456 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001457 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1458 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001459 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001460 self.send_header('Connection', 'close')
1461 self.send_header('Transfer-Encoding', 'chunked')
1462 self.end_headers()
1463 # Chunked encoding: sending all chunks, then final zero-length chunk and
1464 # then final CRLF.
1465 for i in range(0, chunkedSettings['chunksNumber']):
1466 if i > 0:
1467 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1468 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001469 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001470 self.sendChunkHelp('')
1471 return True
1472
initial.commit94958cf2008-07-26 22:42:52 +00001473 def ContentTypeHandler(self):
1474 """Returns a string of html with the given content type. E.g.,
1475 /contenttype?text/css returns an html file with the Content-Type
1476 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001477
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001478 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001479 return False
1480 query_char = self.path.find('?')
1481 content_type = self.path[query_char + 1:].strip()
1482 if not content_type:
1483 content_type = 'text/html'
1484 self.send_response(200)
1485 self.send_header('Content-Type', content_type)
1486 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001487 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001488 return True
1489
creis@google.com2f4f6a42011-03-25 19:44:19 +00001490 def NoContentHandler(self):
1491 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001492
creis@google.com2f4f6a42011-03-25 19:44:19 +00001493 if not self._ShouldHandleRequest("/nocontent"):
1494 return False
1495 self.send_response(204)
1496 self.end_headers()
1497 return True
1498
initial.commit94958cf2008-07-26 22:42:52 +00001499 def ServerRedirectHandler(self):
1500 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001501 '/server-redirect?http://foo.bar/asdf' to redirect to
1502 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001503
1504 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001505 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001506 return False
1507
1508 query_char = self.path.find('?')
1509 if query_char < 0 or len(self.path) <= query_char + 1:
1510 self.sendRedirectHelp(test_name)
1511 return True
1512 dest = self.path[query_char + 1:]
1513
1514 self.send_response(301) # moved permanently
1515 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001516 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001517 self.end_headers()
1518 self.wfile.write('<html><head>')
1519 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1520
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001521 return True
initial.commit94958cf2008-07-26 22:42:52 +00001522
1523 def ClientRedirectHandler(self):
1524 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001525 '/client-redirect?http://foo.bar/asdf' to redirect to
1526 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001527
1528 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001529 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001530 return False
1531
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001532 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001533 if query_char < 0 or len(self.path) <= query_char + 1:
1534 self.sendRedirectHelp(test_name)
1535 return True
1536 dest = self.path[query_char + 1:]
1537
1538 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001539 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001540 self.end_headers()
1541 self.wfile.write('<html><head>')
1542 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1543 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1544
1545 return True
1546
tony@chromium.org03266982010-03-05 03:18:42 +00001547 def MultipartHandler(self):
1548 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001549
tony@chromium.org4cb88302011-09-27 22:13:49 +00001550 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001551 if not self._ShouldHandleRequest(test_name):
1552 return False
1553
1554 num_frames = 10
1555 bound = '12345'
1556 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001557 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001558 'multipart/x-mixed-replace;boundary=' + bound)
1559 self.end_headers()
1560
1561 for i in xrange(num_frames):
1562 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001563 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001564 self.wfile.write('<title>page ' + str(i) + '</title>')
1565 self.wfile.write('page ' + str(i))
1566
1567 self.wfile.write('--' + bound + '--')
1568 return True
1569
tony@chromium.org4cb88302011-09-27 22:13:49 +00001570 def MultipartSlowHandler(self):
1571 """Send a multipart response (3 text/html pages) with a slight delay
1572 between each page. This is similar to how some pages show status using
1573 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001574
tony@chromium.org4cb88302011-09-27 22:13:49 +00001575 test_name = '/multipart-slow'
1576 if not self._ShouldHandleRequest(test_name):
1577 return False
1578
1579 num_frames = 3
1580 bound = '12345'
1581 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001582 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001583 'multipart/x-mixed-replace;boundary=' + bound)
1584 self.end_headers()
1585
1586 for i in xrange(num_frames):
1587 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001588 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001589 time.sleep(0.25)
1590 if i == 2:
1591 self.wfile.write('<title>PASS</title>')
1592 else:
1593 self.wfile.write('<title>page ' + str(i) + '</title>')
1594 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1595
1596 self.wfile.write('--' + bound + '--')
1597 return True
1598
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001599 def GetSSLSessionCacheHandler(self):
1600 """Send a reply containing a log of the session cache operations."""
1601
1602 if not self._ShouldHandleRequest('/ssl-session-cache'):
1603 return False
1604
1605 self.send_response(200)
1606 self.send_header('Content-Type', 'text/plain')
1607 self.end_headers()
1608 try:
1609 for (action, sessionID) in self.server.session_cache.log:
1610 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001611 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001612 self.wfile.write('Pass --https-record-resume in order to use' +
1613 ' this request')
1614 return True
1615
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001616 def CloseSocketHandler(self):
1617 """Closes the socket without sending anything."""
1618
1619 if not self._ShouldHandleRequest('/close-socket'):
1620 return False
1621
1622 self.wfile.close()
1623 return True
1624
initial.commit94958cf2008-07-26 22:42:52 +00001625 def DefaultResponseHandler(self):
1626 """This is the catch-all response handler for requests that aren't handled
1627 by one of the special handlers above.
1628 Note that we specify the content-length as without it the https connection
1629 is not closed properly (and the browser keeps expecting data)."""
1630
1631 contents = "Default response given for path: " + self.path
1632 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001633 self.send_header('Content-Type', 'text/html')
1634 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001635 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001636 if (self.command != 'HEAD'):
1637 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001638 return True
1639
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001640 def RedirectConnectHandler(self):
1641 """Sends a redirect to the CONNECT request for www.redirect.com. This
1642 response is not specified by the RFC, so the browser should not follow
1643 the redirect."""
1644
1645 if (self.path.find("www.redirect.com") < 0):
1646 return False
1647
1648 dest = "http://www.destination.com/foo.js"
1649
1650 self.send_response(302) # moved temporarily
1651 self.send_header('Location', dest)
1652 self.send_header('Connection', 'close')
1653 self.end_headers()
1654 return True
1655
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001656 def ServerAuthConnectHandler(self):
1657 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1658 response doesn't make sense because the proxy server cannot request
1659 server authentication."""
1660
1661 if (self.path.find("www.server-auth.com") < 0):
1662 return False
1663
1664 challenge = 'Basic realm="WallyWorld"'
1665
1666 self.send_response(401) # unauthorized
1667 self.send_header('WWW-Authenticate', challenge)
1668 self.send_header('Connection', 'close')
1669 self.end_headers()
1670 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001671
1672 def DefaultConnectResponseHandler(self):
1673 """This is the catch-all response handler for CONNECT requests that aren't
1674 handled by one of the special handlers above. Real Web servers respond
1675 with 400 to CONNECT requests."""
1676
1677 contents = "Your client has issued a malformed or illegal request."
1678 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001679 self.send_header('Content-Type', 'text/html')
1680 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001681 self.end_headers()
1682 self.wfile.write(contents)
1683 return True
1684
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001685 def DeviceManagementHandler(self):
1686 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001687
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001688 if not self._ShouldHandleRequest("/device_management"):
1689 return False
1690
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001691 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001692
1693 if not self.server._device_management_handler:
1694 import device_management
1695 policy_path = os.path.join(self.server.data_dir, 'device_management')
1696 self.server._device_management_handler = (
joaodasilva@chromium.org3c069da2012-11-20 16:17:15 +00001697 device_management.TestServer(policy_path, self.server.policy_keys))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001698
1699 http_response, raw_reply = (
1700 self.server._device_management_handler.HandleRequest(self.path,
1701 self.headers,
1702 raw_request))
1703 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001704 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001705 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001706 self.end_headers()
1707 self.wfile.write(raw_reply)
1708 return True
1709
initial.commit94958cf2008-07-26 22:42:52 +00001710 # called by the redirect handling function when there is no parameter
1711 def sendRedirectHelp(self, redirect_name):
1712 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001713 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001714 self.end_headers()
1715 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1716 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1717 self.wfile.write('</body></html>')
1718
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001719 # called by chunked handling function
1720 def sendChunkHelp(self, chunk):
1721 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1722 self.wfile.write('%X\r\n' % len(chunk))
1723 self.wfile.write(chunk)
1724 self.wfile.write('\r\n')
1725
akalin@chromium.org154bb132010-11-12 02:20:27 +00001726
1727class SyncPageHandler(BasePageHandler):
1728 """Handler for the main HTTP sync server."""
1729
1730 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001731 get_handlers = [self.ChromiumSyncTimeHandler,
1732 self.ChromiumSyncMigrationOpHandler,
1733 self.ChromiumSyncCredHandler,
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001734 self.ChromiumSyncXmppCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001735 self.ChromiumSyncDisableNotificationsOpHandler,
1736 self.ChromiumSyncEnableNotificationsOpHandler,
1737 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001738 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001739 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001740 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001741 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001742 self.ChromiumSyncCreateSyncedBookmarksOpHandler,
zea@chromium.org261440b2012-12-19 01:56:52 +00001743 self.ChromiumSyncEnableKeystoreEncryptionOpHandler,
1744 self.ChromiumSyncRotateKeystoreKeysOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001745
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001746 post_handlers = [self.ChromiumSyncCommandHandler,
1747 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001748 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001749 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001750 post_handlers, [])
1751
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001752
akalin@chromium.org154bb132010-11-12 02:20:27 +00001753 def ChromiumSyncTimeHandler(self):
1754 """Handle Chromium sync .../time requests.
1755
1756 The syncer sometimes checks server reachability by examining /time.
1757 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001758
akalin@chromium.org154bb132010-11-12 02:20:27 +00001759 test_name = "/chromiumsync/time"
1760 if not self._ShouldHandleRequest(test_name):
1761 return False
1762
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001763 # Chrome hates it if we send a response before reading the request.
1764 if self.headers.getheader('content-length'):
1765 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001766 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001767
akalin@chromium.org154bb132010-11-12 02:20:27 +00001768 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001769 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001770 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001771 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001772 return True
1773
1774 def ChromiumSyncCommandHandler(self):
1775 """Handle a chromiumsync command arriving via http.
1776
1777 This covers all sync protocol commands: authentication, getupdates, and
1778 commit.
1779 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001780
akalin@chromium.org154bb132010-11-12 02:20:27 +00001781 test_name = "/chromiumsync/command"
1782 if not self._ShouldHandleRequest(test_name):
1783 return False
1784
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001785 length = int(self.headers.getheader('content-length'))
1786 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001787 http_response = 200
1788 raw_reply = None
1789 if not self.server.GetAuthenticated():
1790 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001791 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1792 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001793 else:
1794 http_response, raw_reply = self.server.HandleCommand(
1795 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001796
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001797 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001798 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001799 if http_response == 401:
1800 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001801 self.end_headers()
1802 self.wfile.write(raw_reply)
1803 return True
1804
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001805 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001806 test_name = "/chromiumsync/migrate"
1807 if not self._ShouldHandleRequest(test_name):
1808 return False
1809
1810 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1811 self.path)
1812 self.send_response(http_response)
1813 self.send_header('Content-Type', 'text/html')
1814 self.send_header('Content-Length', len(raw_reply))
1815 self.end_headers()
1816 self.wfile.write(raw_reply)
1817 return True
1818
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001819 def ChromiumSyncCredHandler(self):
1820 test_name = "/chromiumsync/cred"
1821 if not self._ShouldHandleRequest(test_name):
1822 return False
1823 try:
1824 query = urlparse.urlparse(self.path)[4]
1825 cred_valid = urlparse.parse_qs(query)['valid']
1826 if cred_valid[0] == 'True':
1827 self.server.SetAuthenticated(True)
1828 else:
1829 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001830 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001831 self.server.SetAuthenticated(False)
1832
1833 http_response = 200
1834 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1835 self.send_response(http_response)
1836 self.send_header('Content-Type', 'text/html')
1837 self.send_header('Content-Length', len(raw_reply))
1838 self.end_headers()
1839 self.wfile.write(raw_reply)
1840 return True
1841
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001842 def ChromiumSyncXmppCredHandler(self):
1843 test_name = "/chromiumsync/xmppcred"
1844 if not self._ShouldHandleRequest(test_name):
1845 return False
1846 xmpp_server = self.server.GetXmppServer()
1847 try:
1848 query = urlparse.urlparse(self.path)[4]
1849 cred_valid = urlparse.parse_qs(query)['valid']
1850 if cred_valid[0] == 'True':
1851 xmpp_server.SetAuthenticated(True)
1852 else:
1853 xmpp_server.SetAuthenticated(False)
1854 except:
1855 xmpp_server.SetAuthenticated(False)
1856
1857 http_response = 200
1858 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated()
1859 self.send_response(http_response)
1860 self.send_header('Content-Type', 'text/html')
1861 self.send_header('Content-Length', len(raw_reply))
1862 self.end_headers()
1863 self.wfile.write(raw_reply)
1864 return True
1865
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001866 def ChromiumSyncDisableNotificationsOpHandler(self):
1867 test_name = "/chromiumsync/disablenotifications"
1868 if not self._ShouldHandleRequest(test_name):
1869 return False
1870 self.server.GetXmppServer().DisableNotifications()
1871 result = 200
1872 raw_reply = ('<html><title>Notifications disabled</title>'
1873 '<H1>Notifications disabled</H1></html>')
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)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001879 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001880
1881 def ChromiumSyncEnableNotificationsOpHandler(self):
1882 test_name = "/chromiumsync/enablenotifications"
1883 if not self._ShouldHandleRequest(test_name):
1884 return False
1885 self.server.GetXmppServer().EnableNotifications()
1886 result = 200
1887 raw_reply = ('<html><title>Notifications enabled</title>'
1888 '<H1>Notifications enabled</H1></html>')
1889 self.send_response(result)
1890 self.send_header('Content-Type', 'text/html')
1891 self.send_header('Content-Length', len(raw_reply))
1892 self.end_headers()
1893 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001894 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001895
1896 def ChromiumSyncSendNotificationOpHandler(self):
1897 test_name = "/chromiumsync/sendnotification"
1898 if not self._ShouldHandleRequest(test_name):
1899 return False
1900 query = urlparse.urlparse(self.path)[4]
1901 query_params = urlparse.parse_qs(query)
1902 channel = ''
1903 data = ''
1904 if 'channel' in query_params:
1905 channel = query_params['channel'][0]
1906 if 'data' in query_params:
1907 data = query_params['data'][0]
1908 self.server.GetXmppServer().SendNotification(channel, data)
1909 result = 200
1910 raw_reply = ('<html><title>Notification sent</title>'
1911 '<H1>Notification sent with channel "%s" '
1912 'and data "%s"</H1></html>'
1913 % (channel, data))
1914 self.send_response(result)
1915 self.send_header('Content-Type', 'text/html')
1916 self.send_header('Content-Length', len(raw_reply))
1917 self.end_headers()
1918 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001919 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001920
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001921 def ChromiumSyncBirthdayErrorOpHandler(self):
1922 test_name = "/chromiumsync/birthdayerror"
1923 if not self._ShouldHandleRequest(test_name):
1924 return False
1925 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1926 self.send_response(result)
1927 self.send_header('Content-Type', 'text/html')
1928 self.send_header('Content-Length', len(raw_reply))
1929 self.end_headers()
1930 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001931 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001932
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001933 def ChromiumSyncTransientErrorOpHandler(self):
1934 test_name = "/chromiumsync/transienterror"
1935 if not self._ShouldHandleRequest(test_name):
1936 return False
1937 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1938 self.send_response(result)
1939 self.send_header('Content-Type', 'text/html')
1940 self.send_header('Content-Length', len(raw_reply))
1941 self.end_headers()
1942 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001943 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001944
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001945 def ChromiumSyncErrorOpHandler(self):
1946 test_name = "/chromiumsync/error"
1947 if not self._ShouldHandleRequest(test_name):
1948 return False
1949 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1950 self.path)
1951 self.send_response(result)
1952 self.send_header('Content-Type', 'text/html')
1953 self.send_header('Content-Length', len(raw_reply))
1954 self.end_headers()
1955 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001956 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001957
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001958 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1959 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001960 if not self._ShouldHandleRequest(test_name):
1961 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001962 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001963 self.send_response(result)
1964 self.send_header('Content-Type', 'text/html')
1965 self.send_header('Content-Length', len(raw_reply))
1966 self.end_headers()
1967 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001968 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001969
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001970 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1971 test_name = "/chromiumsync/createsyncedbookmarks"
1972 if not self._ShouldHandleRequest(test_name):
1973 return False
1974 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1975 self.send_response(result)
1976 self.send_header('Content-Type', 'text/html')
1977 self.send_header('Content-Length', len(raw_reply))
1978 self.end_headers()
1979 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001980 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001981
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001982 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
1983 test_name = "/chromiumsync/enablekeystoreencryption"
1984 if not self._ShouldHandleRequest(test_name):
1985 return False
1986 result, raw_reply = (
1987 self.server._sync_handler.HandleEnableKeystoreEncryption())
1988 self.send_response(result)
1989 self.send_header('Content-Type', 'text/html')
1990 self.send_header('Content-Length', len(raw_reply))
1991 self.end_headers()
1992 self.wfile.write(raw_reply)
1993 return True
1994
zea@chromium.org261440b2012-12-19 01:56:52 +00001995 def ChromiumSyncRotateKeystoreKeysOpHandler(self):
1996 test_name = "/chromiumsync/rotatekeystorekeys"
1997 if not self._ShouldHandleRequest(test_name):
1998 return False
1999 result, raw_reply = (
2000 self.server._sync_handler.HandleRotateKeystoreKeys())
2001 self.send_response(result)
2002 self.send_header('Content-Type', 'text/html')
2003 self.send_header('Content-Length', len(raw_reply))
2004 self.end_headers()
2005 self.wfile.write(raw_reply)
2006 return True
2007
akalin@chromium.org154bb132010-11-12 02:20:27 +00002008
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002009class OCSPHandler(BasePageHandler):
2010 def __init__(self, request, client_address, socket_server):
2011 handlers = [self.OCSPResponse]
2012 self.ocsp_response = socket_server.ocsp_response
2013 BasePageHandler.__init__(self, request, client_address, socket_server,
2014 [], handlers, [], handlers, [])
2015
2016 def OCSPResponse(self):
2017 self.send_response(200)
2018 self.send_header('Content-Type', 'application/ocsp-response')
2019 self.send_header('Content-Length', str(len(self.ocsp_response)))
2020 self.end_headers()
2021
2022 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002023
mattm@chromium.org830a3712012-11-07 23:00:07 +00002024
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002025class TCPEchoHandler(SocketServer.BaseRequestHandler):
2026 """The RequestHandler class for TCP echo server.
2027
2028 It is instantiated once per connection to the server, and overrides the
2029 handle() method to implement communication to the client.
2030 """
2031
2032 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002033 """Handles the request from the client and constructs a response."""
2034
2035 data = self.request.recv(65536).strip()
2036 # Verify the "echo request" message received from the client. Send back
2037 # "echo response" message if "echo request" message is valid.
2038 try:
2039 return_data = echo_message.GetEchoResponseData(data)
2040 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002041 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002042 except ValueError:
2043 return
2044
2045 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002046
2047
2048class UDPEchoHandler(SocketServer.BaseRequestHandler):
2049 """The RequestHandler class for UDP echo server.
2050
2051 It is instantiated once per connection to the server, and overrides the
2052 handle() method to implement communication to the client.
2053 """
2054
2055 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002056 """Handles the request from the client and constructs a response."""
2057
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002058 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002059 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002060 # Verify the "echo request" message received from the client. Send back
2061 # "echo response" message if "echo request" message is valid.
2062 try:
2063 return_data = echo_message.GetEchoResponseData(data)
2064 if not return_data:
2065 return
2066 except ValueError:
2067 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002068 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002069
2070
bashi@chromium.org33233532012-09-08 17:37:24 +00002071class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2072 """A request handler that behaves as a proxy server which requires
2073 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2074 """
2075
2076 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2077
2078 def parse_request(self):
2079 """Overrides parse_request to check credential."""
2080
2081 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2082 return False
2083
2084 auth = self.headers.getheader('Proxy-Authorization')
2085 if auth != self._AUTH_CREDENTIAL:
2086 self.send_response(407)
2087 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2088 self.end_headers()
2089 return False
2090
2091 return True
2092
2093 def _start_read_write(self, sock):
2094 sock.setblocking(0)
2095 self.request.setblocking(0)
2096 rlist = [self.request, sock]
2097 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002098 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002099 if errors:
2100 self.send_response(500)
2101 self.end_headers()
2102 return
2103 for s in ready_sockets:
2104 received = s.recv(1024)
2105 if len(received) == 0:
2106 return
2107 if s == self.request:
2108 other = sock
2109 else:
2110 other = self.request
2111 other.send(received)
2112
2113 def _do_common_method(self):
2114 url = urlparse.urlparse(self.path)
2115 port = url.port
2116 if not port:
2117 if url.scheme == 'http':
2118 port = 80
2119 elif url.scheme == 'https':
2120 port = 443
2121 if not url.hostname or not port:
2122 self.send_response(400)
2123 self.end_headers()
2124 return
2125
2126 if len(url.path) == 0:
2127 path = '/'
2128 else:
2129 path = url.path
2130 if len(url.query) > 0:
2131 path = '%s?%s' % (url.path, url.query)
2132
2133 sock = None
2134 try:
2135 sock = socket.create_connection((url.hostname, port))
2136 sock.send('%s %s %s\r\n' % (
2137 self.command, path, self.protocol_version))
2138 for header in self.headers.headers:
2139 header = header.strip()
2140 if (header.lower().startswith('connection') or
2141 header.lower().startswith('proxy')):
2142 continue
2143 sock.send('%s\r\n' % header)
2144 sock.send('\r\n')
2145 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002146 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002147 self.send_response(500)
2148 self.end_headers()
2149 finally:
2150 if sock is not None:
2151 sock.close()
2152
2153 def do_CONNECT(self):
2154 try:
2155 pos = self.path.rfind(':')
2156 host = self.path[:pos]
2157 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002158 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002159 self.send_response(400)
2160 self.end_headers()
2161
2162 try:
2163 sock = socket.create_connection((host, port))
2164 self.send_response(200, 'Connection established')
2165 self.end_headers()
2166 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002167 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002168 self.send_response(500)
2169 self.end_headers()
2170 finally:
2171 sock.close()
2172
2173 def do_GET(self):
2174 self._do_common_method()
2175
2176 def do_HEAD(self):
2177 self._do_common_method()
2178
2179
mattm@chromium.org830a3712012-11-07 23:00:07 +00002180class ServerRunner(testserver_base.TestServerRunner):
2181 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002182
mattm@chromium.org830a3712012-11-07 23:00:07 +00002183 def __init__(self):
2184 super(ServerRunner, self).__init__()
2185 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002186
mattm@chromium.org830a3712012-11-07 23:00:07 +00002187 def __make_data_dir(self):
2188 if self.options.data_dir:
2189 if not os.path.isdir(self.options.data_dir):
2190 raise testserver_base.OptionError('specified data dir not found: ' +
2191 self.options.data_dir + ' exiting...')
2192 my_data_dir = self.options.data_dir
2193 else:
2194 # Create the default path to our data dir, relative to the exe dir.
2195 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2196 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002197
mattm@chromium.org830a3712012-11-07 23:00:07 +00002198 #TODO(ibrar): Must use Find* funtion defined in google\tools
2199 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002200
mattm@chromium.org830a3712012-11-07 23:00:07 +00002201 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002202
mattm@chromium.org830a3712012-11-07 23:00:07 +00002203 def create_server(self, server_data):
2204 port = self.options.port
2205 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002206
mattm@chromium.org830a3712012-11-07 23:00:07 +00002207 if self.options.server_type == SERVER_HTTP:
2208 if self.options.https:
2209 pem_cert_and_key = None
2210 if self.options.cert_and_key_file:
2211 if not os.path.isfile(self.options.cert_and_key_file):
2212 raise testserver_base.OptionError(
2213 'specified server cert file not found: ' +
2214 self.options.cert_and_key_file + ' exiting...')
2215 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002216 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002217 # generate a new certificate and run an OCSP server for it.
2218 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2219 print ('OCSP server started on %s:%d...' %
2220 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002221
mattm@chromium.org830a3712012-11-07 23:00:07 +00002222 ocsp_der = None
2223 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002224
mattm@chromium.org830a3712012-11-07 23:00:07 +00002225 if self.options.ocsp == 'ok':
2226 ocsp_state = minica.OCSP_STATE_GOOD
2227 elif self.options.ocsp == 'revoked':
2228 ocsp_state = minica.OCSP_STATE_REVOKED
2229 elif self.options.ocsp == 'invalid':
2230 ocsp_state = minica.OCSP_STATE_INVALID
2231 elif self.options.ocsp == 'unauthorized':
2232 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2233 elif self.options.ocsp == 'unknown':
2234 ocsp_state = minica.OCSP_STATE_UNKNOWN
2235 else:
2236 raise testserver_base.OptionError('unknown OCSP status: ' +
2237 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002238
mattm@chromium.org830a3712012-11-07 23:00:07 +00002239 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2240 subject = "127.0.0.1",
2241 ocsp_url = ("http://%s:%d/ocsp" %
2242 (host, self.__ocsp_server.server_port)),
2243 ocsp_state = ocsp_state)
2244
2245 self.__ocsp_server.ocsp_response = ocsp_der
2246
2247 for ca_cert in self.options.ssl_client_ca:
2248 if not os.path.isfile(ca_cert):
2249 raise testserver_base.OptionError(
2250 'specified trusted client CA file not found: ' + ca_cert +
2251 ' exiting...')
2252 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2253 self.options.ssl_client_auth,
2254 self.options.ssl_client_ca,
2255 self.options.ssl_bulk_cipher,
2256 self.options.record_resume,
2257 self.options.tls_intolerant)
2258 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2259 else:
2260 server = HTTPServer((host, port), TestPageHandler)
2261 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2262
2263 server.data_dir = self.__make_data_dir()
2264 server.file_root_url = self.options.file_root_url
2265 server_data['port'] = server.server_port
2266 server._device_management_handler = None
2267 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00002268 elif self.options.server_type == SERVER_WEBSOCKET:
2269 # Launch pywebsocket via WebSocketServer.
2270 logger = logging.getLogger()
2271 logger.addHandler(logging.StreamHandler())
2272 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2273 # is required to work correctly. It should be fixed from pywebsocket side.
2274 os.chdir(self.__make_data_dir())
2275 websocket_options = WebSocketOptions(host, port, '.')
2276 if self.options.cert_and_key_file:
2277 websocket_options.use_tls = True
2278 websocket_options.private_key = self.options.cert_and_key_file
2279 websocket_options.certificate = self.options.cert_and_key_file
2280 if self.options.ssl_client_auth:
2281 websocket_options.tls_client_auth = True
2282 if len(self.options.ssl_client_ca) != 1:
2283 raise testserver_base.OptionError(
2284 'one trusted client CA file should be specified')
2285 if not os.path.isfile(self.options.ssl_client_ca[0]):
2286 raise testserver_base.OptionError(
2287 'specified trusted client CA file not found: ' +
2288 self.options.ssl_client_ca[0] + ' exiting...')
2289 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2290 server = WebSocketServer(websocket_options)
2291 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2292 server_data['port'] = server.server_port
2293 elif self.options.server_type == SERVER_SYNC:
2294 xmpp_port = self.options.xmpp_port
2295 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2296 print 'Sync HTTP server started on port %d...' % server.server_port
2297 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2298 server_data['port'] = server.server_port
2299 server_data['xmpp_port'] = server.xmpp_port
2300 elif self.options.server_type == SERVER_TCP_ECHO:
2301 # Used for generating the key (randomly) that encodes the "echo request"
2302 # message.
2303 random.seed()
2304 server = TCPEchoServer((host, port), TCPEchoHandler)
2305 print 'Echo TCP server started on port %d...' % server.server_port
2306 server_data['port'] = server.server_port
2307 elif self.options.server_type == SERVER_UDP_ECHO:
2308 # Used for generating the key (randomly) that encodes the "echo request"
2309 # message.
2310 random.seed()
2311 server = UDPEchoServer((host, port), UDPEchoHandler)
2312 print 'Echo UDP server started on port %d...' % server.server_port
2313 server_data['port'] = server.server_port
2314 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2315 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2316 print 'BasicAuthProxy server started on port %d...' % server.server_port
2317 server_data['port'] = server.server_port
2318 elif self.options.server_type == SERVER_FTP:
2319 my_data_dir = self.__make_data_dir()
2320
2321 # Instantiate a dummy authorizer for managing 'virtual' users
2322 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2323
2324 # Define a new user having full r/w permissions and a read-only
2325 # anonymous user
2326 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2327
2328 authorizer.add_anonymous(my_data_dir)
2329
2330 # Instantiate FTP handler class
2331 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2332 ftp_handler.authorizer = authorizer
2333
2334 # Define a customized banner (string returned when client connects)
2335 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2336 pyftpdlib.ftpserver.__ver__)
2337
2338 # Instantiate FTP server class and listen to address:port
2339 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2340 server_data['port'] = server.socket.getsockname()[1]
2341 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002342 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002343 raise testserver_base.OptionError('unknown server type' +
2344 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002345
mattm@chromium.org830a3712012-11-07 23:00:07 +00002346 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002347
mattm@chromium.org830a3712012-11-07 23:00:07 +00002348 def run_server(self):
2349 if self.__ocsp_server:
2350 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002351
mattm@chromium.org830a3712012-11-07 23:00:07 +00002352 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002353
mattm@chromium.org830a3712012-11-07 23:00:07 +00002354 if self.__ocsp_server:
2355 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002356
mattm@chromium.org830a3712012-11-07 23:00:07 +00002357 def add_options(self):
2358 testserver_base.TestServerRunner.add_options(self)
2359 self.option_parser.add_option('-f', '--ftp', action='store_const',
2360 const=SERVER_FTP, default=SERVER_HTTP,
2361 dest='server_type',
2362 help='start up an FTP server.')
2363 self.option_parser.add_option('--sync', action='store_const',
2364 const=SERVER_SYNC, default=SERVER_HTTP,
2365 dest='server_type',
2366 help='start up a sync server.')
2367 self.option_parser.add_option('--tcp-echo', action='store_const',
2368 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2369 dest='server_type',
2370 help='start up a tcp echo server.')
2371 self.option_parser.add_option('--udp-echo', action='store_const',
2372 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2373 dest='server_type',
2374 help='start up a udp echo server.')
2375 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2376 const=SERVER_BASIC_AUTH_PROXY,
2377 default=SERVER_HTTP, dest='server_type',
2378 help='start up a proxy server which requires '
2379 'basic authentication.')
2380 self.option_parser.add_option('--websocket', action='store_const',
2381 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2382 dest='server_type',
2383 help='start up a WebSocket server.')
2384 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2385 help='Port used by the XMPP server. If '
2386 'unspecified, the XMPP server will listen on '
2387 'an ephemeral port.')
2388 self.option_parser.add_option('--data-dir', dest='data_dir',
2389 help='Directory from which to read the '
2390 'files.')
2391 self.option_parser.add_option('--https', action='store_true',
2392 dest='https', help='Specify that https '
2393 'should be used.')
2394 self.option_parser.add_option('--cert-and-key-file',
2395 dest='cert_and_key_file', help='specify the '
2396 'path to the file containing the certificate '
2397 'and private key for the server in PEM '
2398 'format')
2399 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2400 help='The type of OCSP response generated '
2401 'for the automatically generated '
2402 'certificate. One of [ok,revoked,invalid]')
2403 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2404 default='0', type='int',
2405 help='If nonzero, certain TLS connections '
2406 'will be aborted in order to test version '
2407 'fallback. 1 means all TLS versions will be '
2408 'aborted. 2 means TLS 1.1 or higher will be '
2409 'aborted. 3 means TLS 1.2 or higher will be '
2410 'aborted.')
2411 self.option_parser.add_option('--https-record-resume',
2412 dest='record_resume', const=True,
2413 default=False, action='store_const',
2414 help='Record resumption cache events rather '
2415 'than resuming as normal. Allows the use of '
2416 'the /ssl-session-cache request')
2417 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2418 help='Require SSL client auth on every '
2419 'connection.')
2420 self.option_parser.add_option('--ssl-client-ca', action='append',
2421 default=[], help='Specify that the client '
2422 'certificate request should include the CA '
2423 'named in the subject of the DER-encoded '
2424 'certificate contained in the specified '
2425 'file. This option may appear multiple '
2426 'times, indicating multiple CA names should '
2427 'be sent in the request.')
2428 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2429 help='Specify the bulk encryption '
2430 'algorithm(s) that will be accepted by the '
2431 'SSL server. Valid values are "aes256", '
2432 '"aes128", "3des", "rc4". If omitted, all '
2433 'algorithms will be used. This option may '
2434 'appear multiple times, indicating '
2435 'multiple algorithms should be enabled.');
2436 self.option_parser.add_option('--file-root-url', default='/files/',
2437 help='Specify a root URL for files served.')
2438 self.option_parser.add_option('--policy-key', action='append',
2439 dest='policy_keys',
2440 help='Specify a path to a PEM-encoded '
2441 'private key to use for policy signing. May '
2442 'be specified multiple times in order to '
2443 'load multipe keys into the server. If the '
2444 'server has multiple keys, it will rotate '
2445 'through them in at each request a '
2446 'round-robin fashion. The server will '
2447 'generate a random key if none is specified '
2448 'on the command line.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002449
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002450
initial.commit94958cf2008-07-26 22:42:52 +00002451if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002452 sys.exit(ServerRunner().main())