blob: 91fe6e1917197331f31d18cc066a76c6e50ffe8b [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',
497 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000498 }
initial.commit94958cf2008-07-26 22:42:52 +0000499 self._default_mime_type = 'text/html'
500
akalin@chromium.org154bb132010-11-12 02:20:27 +0000501 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000502 connect_handlers, get_handlers, head_handlers,
503 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000504
initial.commit94958cf2008-07-26 22:42:52 +0000505 def GetMIMETypeFromName(self, file_name):
506 """Returns the mime type for the specified file_name. So far it only looks
507 at the file extension."""
508
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000509 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000510 if len(extension) == 0:
511 # no extension.
512 return self._default_mime_type
513
ericroman@google.comc17ca532009-05-07 03:51:05 +0000514 # extension starts with a dot, so we need to remove it
515 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000516
initial.commit94958cf2008-07-26 22:42:52 +0000517 def NoCacheMaxAgeTimeHandler(self):
518 """This request handler yields a page with the title set to the current
519 system time, and no caching requested."""
520
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000521 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000522 return False
523
524 self.send_response(200)
525 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000526 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000527 self.end_headers()
528
maruel@google.come250a9b2009-03-10 17:39:46 +0000529 self.wfile.write('<html><head><title>%s</title></head></html>' %
530 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000531
532 return True
533
534 def NoCacheTimeHandler(self):
535 """This request handler yields a page with the title set to the current
536 system time, and no caching requested."""
537
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000538 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000539 return False
540
541 self.send_response(200)
542 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000543 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000544 self.end_headers()
545
maruel@google.come250a9b2009-03-10 17:39:46 +0000546 self.wfile.write('<html><head><title>%s</title></head></html>' %
547 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000548
549 return True
550
551 def CacheTimeHandler(self):
552 """This request handler yields a page with the title set to the current
553 system time, and allows caching for one minute."""
554
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000555 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000556 return False
557
558 self.send_response(200)
559 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000560 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000561 self.end_headers()
562
maruel@google.come250a9b2009-03-10 17:39:46 +0000563 self.wfile.write('<html><head><title>%s</title></head></html>' %
564 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000565
566 return True
567
568 def CacheExpiresHandler(self):
569 """This request handler yields a page with the title set to the current
570 system time, and set the page to expire on 1 Jan 2099."""
571
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000572 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000573 return False
574
575 self.send_response(200)
576 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000577 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000578 self.end_headers()
579
maruel@google.come250a9b2009-03-10 17:39:46 +0000580 self.wfile.write('<html><head><title>%s</title></head></html>' %
581 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000582
583 return True
584
585 def CacheProxyRevalidateHandler(self):
586 """This request handler yields a page with the title set to the current
587 system time, and allows caching for 60 seconds"""
588
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000589 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000590 return False
591
592 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000593 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000594 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
595 self.end_headers()
596
maruel@google.come250a9b2009-03-10 17:39:46 +0000597 self.wfile.write('<html><head><title>%s</title></head></html>' %
598 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000599
600 return True
601
602 def CachePrivateHandler(self):
603 """This request handler yields a page with the title set to the current
604 system time, and allows caching for 5 seconds."""
605
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000606 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000607 return False
608
609 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000610 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000611 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000612 self.end_headers()
613
maruel@google.come250a9b2009-03-10 17:39:46 +0000614 self.wfile.write('<html><head><title>%s</title></head></html>' %
615 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000616
617 return True
618
619 def CachePublicHandler(self):
620 """This request handler yields a page with the title set to the current
621 system time, and allows caching for 5 seconds."""
622
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000623 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000624 return False
625
626 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000627 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000628 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000629 self.end_headers()
630
maruel@google.come250a9b2009-03-10 17:39:46 +0000631 self.wfile.write('<html><head><title>%s</title></head></html>' %
632 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000633
634 return True
635
636 def CacheSMaxAgeHandler(self):
637 """This request handler yields a page with the title set to the current
638 system time, and does not allow for caching."""
639
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000640 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000641 return False
642
643 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000644 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000645 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
646 self.end_headers()
647
maruel@google.come250a9b2009-03-10 17:39:46 +0000648 self.wfile.write('<html><head><title>%s</title></head></html>' %
649 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000650
651 return True
652
653 def CacheMustRevalidateHandler(self):
654 """This request handler yields a page with the title set to the current
655 system time, and does not allow caching."""
656
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000657 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000658 return False
659
660 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000661 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000662 self.send_header('Cache-Control', 'must-revalidate')
663 self.end_headers()
664
maruel@google.come250a9b2009-03-10 17:39:46 +0000665 self.wfile.write('<html><head><title>%s</title></head></html>' %
666 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000667
668 return True
669
670 def CacheMustRevalidateMaxAgeHandler(self):
671 """This request handler yields a page with the title set to the current
672 system time, and does not allow caching event though max-age of 60
673 seconds is specified."""
674
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000675 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000676 return False
677
678 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000679 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000680 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
681 self.end_headers()
682
maruel@google.come250a9b2009-03-10 17:39:46 +0000683 self.wfile.write('<html><head><title>%s</title></head></html>' %
684 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000685
686 return True
687
initial.commit94958cf2008-07-26 22:42:52 +0000688 def CacheNoStoreHandler(self):
689 """This request handler yields a page with the title set to the current
690 system time, and does not allow the page to be stored."""
691
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000692 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000693 return False
694
695 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000696 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000697 self.send_header('Cache-Control', 'no-store')
698 self.end_headers()
699
maruel@google.come250a9b2009-03-10 17:39:46 +0000700 self.wfile.write('<html><head><title>%s</title></head></html>' %
701 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000702
703 return True
704
705 def CacheNoStoreMaxAgeHandler(self):
706 """This request handler yields a page with the title set to the current
707 system time, and does not allow the page to be stored even though max-age
708 of 60 seconds is specified."""
709
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000710 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000711 return False
712
713 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000714 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000715 self.send_header('Cache-Control', 'max-age=60, no-store')
716 self.end_headers()
717
maruel@google.come250a9b2009-03-10 17:39:46 +0000718 self.wfile.write('<html><head><title>%s</title></head></html>' %
719 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000720
721 return True
722
723
724 def CacheNoTransformHandler(self):
725 """This request handler yields a page with the title set to the current
726 system time, and does not allow the content to transformed during
727 user-agent caching"""
728
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000729 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000730 return False
731
732 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000733 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000734 self.send_header('Cache-Control', 'no-transform')
735 self.end_headers()
736
maruel@google.come250a9b2009-03-10 17:39:46 +0000737 self.wfile.write('<html><head><title>%s</title></head></html>' %
738 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000739
740 return True
741
742 def EchoHeader(self):
743 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000744
ananta@chromium.org219b2062009-10-23 16:09:41 +0000745 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000746
ananta@chromium.org56812d02011-04-07 17:52:05 +0000747 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000748 """This function echoes back the value of a specific request header while
749 allowing caching for 16 hours."""
750
ananta@chromium.org56812d02011-04-07 17:52:05 +0000751 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000752
753 def EchoHeaderHelper(self, echo_header):
754 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000755
ananta@chromium.org219b2062009-10-23 16:09:41 +0000756 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000757 return False
758
759 query_char = self.path.find('?')
760 if query_char != -1:
761 header_name = self.path[query_char+1:]
762
763 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000764 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000765 if echo_header == '/echoheadercache':
766 self.send_header('Cache-control', 'max-age=60000')
767 else:
768 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000769 # insert a vary header to properly indicate that the cachability of this
770 # request is subject to value of the request header being echoed.
771 if len(header_name) > 0:
772 self.send_header('Vary', header_name)
773 self.end_headers()
774
775 if len(header_name) > 0:
776 self.wfile.write(self.headers.getheader(header_name))
777
778 return True
779
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000780 def ReadRequestBody(self):
781 """This function reads the body of the current HTTP request, handling
782 both plain and chunked transfer encoded requests."""
783
784 if self.headers.getheader('transfer-encoding') != 'chunked':
785 length = int(self.headers.getheader('content-length'))
786 return self.rfile.read(length)
787
788 # Read the request body as chunks.
789 body = ""
790 while True:
791 line = self.rfile.readline()
792 length = int(line, 16)
793 if length == 0:
794 self.rfile.readline()
795 break
796 body += self.rfile.read(length)
797 self.rfile.read(2)
798 return body
799
initial.commit94958cf2008-07-26 22:42:52 +0000800 def EchoHandler(self):
801 """This handler just echoes back the payload of the request, for testing
802 form submission."""
803
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000804 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000805 return False
806
807 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000808 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000809 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000810 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000811 return True
812
813 def EchoTitleHandler(self):
814 """This handler is like Echo, but sets the page title to the request."""
815
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000816 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000817 return False
818
819 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000820 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000821 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000822 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000823 self.wfile.write('<html><head><title>')
824 self.wfile.write(request)
825 self.wfile.write('</title></head></html>')
826 return True
827
828 def EchoAllHandler(self):
829 """This handler yields a (more) human-readable page listing information
830 about the request header & contents."""
831
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000832 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000833 return False
834
835 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000836 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000837 self.end_headers()
838 self.wfile.write('<html><head><style>'
839 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
840 '</style></head><body>'
841 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000842 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000843 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000844
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000845 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000846 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000847 params = cgi.parse_qs(qs, keep_blank_values=1)
848
849 for param in params:
850 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000851
852 self.wfile.write('</pre>')
853
854 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
855
856 self.wfile.write('</body></html>')
857 return True
858
859 def DownloadHandler(self):
860 """This handler sends a downloadable file with or without reporting
861 the size (6K)."""
862
863 if self.path.startswith("/download-unknown-size"):
864 send_length = False
865 elif self.path.startswith("/download-known-size"):
866 send_length = True
867 else:
868 return False
869
870 #
871 # The test which uses this functionality is attempting to send
872 # small chunks of data to the client. Use a fairly large buffer
873 # so that we'll fill chrome's IO buffer enough to force it to
874 # actually write the data.
875 # See also the comments in the client-side of this test in
876 # download_uitest.cc
877 #
878 size_chunk1 = 35*1024
879 size_chunk2 = 10*1024
880
881 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000882 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000883 self.send_header('Cache-Control', 'max-age=0')
884 if send_length:
885 self.send_header('Content-Length', size_chunk1 + size_chunk2)
886 self.end_headers()
887
888 # First chunk of data:
889 self.wfile.write("*" * size_chunk1)
890 self.wfile.flush()
891
892 # handle requests until one of them clears this flag.
893 self.server.waitForDownload = True
894 while self.server.waitForDownload:
895 self.server.handle_request()
896
897 # Second chunk of data:
898 self.wfile.write("*" * size_chunk2)
899 return True
900
901 def DownloadFinishHandler(self):
902 """This handler just tells the server to finish the current download."""
903
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000904 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000905 return False
906
907 self.server.waitForDownload = False
908 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000909 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000910 self.send_header('Cache-Control', 'max-age=0')
911 self.end_headers()
912 return True
913
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000914 def _ReplaceFileData(self, data, query_parameters):
915 """Replaces matching substrings in a file.
916
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000917 If the 'replace_text' URL query parameter is present, it is expected to be
918 of the form old_text:new_text, which indicates that any old_text strings in
919 the file are replaced with new_text. Multiple 'replace_text' parameters may
920 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000921
922 If the parameters are not present, |data| is returned.
923 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000924
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000925 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000926 replace_text_values = query_dict.get('replace_text', [])
927 for replace_text_value in replace_text_values:
928 replace_text_args = replace_text_value.split(':')
929 if len(replace_text_args) != 2:
930 raise ValueError(
931 'replace_text must be of form old_text:new_text. Actual value: %s' %
932 replace_text_value)
933 old_text_b64, new_text_b64 = replace_text_args
934 old_text = base64.urlsafe_b64decode(old_text_b64)
935 new_text = base64.urlsafe_b64decode(new_text_b64)
936 data = data.replace(old_text, new_text)
937 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000938
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000939 def ZipFileHandler(self):
940 """This handler sends the contents of the requested file in compressed form.
941 Can pass in a parameter that specifies that the content length be
942 C - the compressed size (OK),
943 U - the uncompressed size (Non-standard, but handled),
944 S - less than compressed (OK because we keep going),
945 M - larger than compressed but less than uncompressed (an error),
946 L - larger than uncompressed (an error)
947 Example: compressedfiles/Picture_1.doc?C
948 """
949
950 prefix = "/compressedfiles/"
951 if not self.path.startswith(prefix):
952 return False
953
954 # Consume a request body if present.
955 if self.command == 'POST' or self.command == 'PUT' :
956 self.ReadRequestBody()
957
958 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
959
960 if not query in ('C', 'U', 'S', 'M', 'L'):
961 return False
962
963 sub_path = url_path[len(prefix):]
964 entries = sub_path.split('/')
965 file_path = os.path.join(self.server.data_dir, *entries)
966 if os.path.isdir(file_path):
967 file_path = os.path.join(file_path, 'index.html')
968
969 if not os.path.isfile(file_path):
970 print "File not found " + sub_path + " full path:" + file_path
971 self.send_error(404)
972 return True
973
974 f = open(file_path, "rb")
975 data = f.read()
976 uncompressed_len = len(data)
977 f.close()
978
979 # Compress the data.
980 data = zlib.compress(data)
981 compressed_len = len(data)
982
983 content_length = compressed_len
984 if query == 'U':
985 content_length = uncompressed_len
986 elif query == 'S':
987 content_length = compressed_len / 2
988 elif query == 'M':
989 content_length = (compressed_len + uncompressed_len) / 2
990 elif query == 'L':
991 content_length = compressed_len + uncompressed_len
992
993 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000994 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000995 self.send_header('Content-encoding', 'deflate')
996 self.send_header('Connection', 'close')
997 self.send_header('Content-Length', content_length)
998 self.send_header('ETag', '\'' + file_path + '\'')
999 self.end_headers()
1000
1001 self.wfile.write(data)
1002
1003 return True
1004
initial.commit94958cf2008-07-26 22:42:52 +00001005 def FileHandler(self):
1006 """This handler sends the contents of the requested file. Wow, it's like
1007 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001008
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001009 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +00001010 if not self.path.startswith(prefix):
1011 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001012 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +00001013
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001014 def PostOnlyFileHandler(self):
1015 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001016
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +00001017 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001018 if not self.path.startswith(prefix):
1019 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001020 return self._FileHandlerHelper(prefix)
1021
1022 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001023 request_body = ''
1024 if self.command == 'POST' or self.command == 'PUT':
1025 # Consume a request body if present.
1026 request_body = self.ReadRequestBody()
1027
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001028 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001029 query_dict = cgi.parse_qs(query)
1030
1031 expected_body = query_dict.get('expected_body', [])
1032 if expected_body and request_body not in expected_body:
1033 self.send_response(404)
1034 self.end_headers()
1035 self.wfile.write('')
1036 return True
1037
1038 expected_headers = query_dict.get('expected_headers', [])
1039 for expected_header in expected_headers:
1040 header_name, expected_value = expected_header.split(':')
1041 if self.headers.getheader(header_name) != expected_value:
1042 self.send_response(404)
1043 self.end_headers()
1044 self.wfile.write('')
1045 return True
1046
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001047 sub_path = url_path[len(prefix):]
1048 entries = sub_path.split('/')
1049 file_path = os.path.join(self.server.data_dir, *entries)
1050 if os.path.isdir(file_path):
1051 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001052
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001053 if not os.path.isfile(file_path):
1054 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001055 self.send_error(404)
1056 return True
1057
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001058 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001059 data = f.read()
1060 f.close()
1061
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001062 data = self._ReplaceFileData(data, query)
1063
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001064 old_protocol_version = self.protocol_version
1065
initial.commit94958cf2008-07-26 22:42:52 +00001066 # If file.mock-http-headers exists, it contains the headers we
1067 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001068 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001069 if os.path.isfile(headers_path):
1070 f = open(headers_path, "r")
1071
1072 # "HTTP/1.1 200 OK"
1073 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001074 http_major, http_minor, status_code = re.findall(
1075 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1076 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001077 self.send_response(int(status_code))
1078
1079 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001080 header_values = re.findall('(\S+):\s*(.*)', line)
1081 if len(header_values) > 0:
1082 # "name: value"
1083 name, value = header_values[0]
1084 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001085 f.close()
1086 else:
1087 # Could be more generic once we support mime-type sniffing, but for
1088 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001089
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001090 range_header = self.headers.get('Range')
1091 if range_header and range_header.startswith('bytes='):
1092 # Note this doesn't handle all valid byte range_header values (i.e.
1093 # left open ended ones), just enough for what we needed so far.
1094 range_header = range_header[6:].split('-')
1095 start = int(range_header[0])
1096 if range_header[1]:
1097 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001098 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001099 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001100
1101 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001102 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1103 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001104 self.send_header('Content-Range', content_range)
1105 data = data[start: end + 1]
1106 else:
1107 self.send_response(200)
1108
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001109 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001110 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001111 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001112 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001113 self.end_headers()
1114
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001115 if (self.command != 'HEAD'):
1116 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001117
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001118 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001119 return True
1120
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001121 def SetCookieHandler(self):
1122 """This handler just sets a cookie, for testing cookie handling."""
1123
1124 if not self._ShouldHandleRequest("/set-cookie"):
1125 return False
1126
1127 query_char = self.path.find('?')
1128 if query_char != -1:
1129 cookie_values = self.path[query_char + 1:].split('&')
1130 else:
1131 cookie_values = ("",)
1132 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001133 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001134 for cookie_value in cookie_values:
1135 self.send_header('Set-Cookie', '%s' % cookie_value)
1136 self.end_headers()
1137 for cookie_value in cookie_values:
1138 self.wfile.write('%s' % cookie_value)
1139 return True
1140
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001141 def SetManyCookiesHandler(self):
1142 """This handler just sets a given number of cookies, for testing handling
1143 of large numbers of cookies."""
1144
1145 if not self._ShouldHandleRequest("/set-many-cookies"):
1146 return False
1147
1148 query_char = self.path.find('?')
1149 if query_char != -1:
1150 num_cookies = int(self.path[query_char + 1:])
1151 else:
1152 num_cookies = 0
1153 self.send_response(200)
1154 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001155 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001156 self.send_header('Set-Cookie', 'a=')
1157 self.end_headers()
1158 self.wfile.write('%d cookies were sent' % num_cookies)
1159 return True
1160
mattm@chromium.org983fc462012-06-30 00:52:08 +00001161 def ExpectAndSetCookieHandler(self):
1162 """Expects some cookies to be sent, and if they are, sets more cookies.
1163
1164 The expect parameter specifies a required cookie. May be specified multiple
1165 times.
1166 The set parameter specifies a cookie to set if all required cookies are
1167 preset. May be specified multiple times.
1168 The data parameter specifies the response body data to be returned."""
1169
1170 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1171 return False
1172
1173 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1174 query_dict = cgi.parse_qs(query)
1175 cookies = set()
1176 if 'Cookie' in self.headers:
1177 cookie_header = self.headers.getheader('Cookie')
1178 cookies.update([s.strip() for s in cookie_header.split(';')])
1179 got_all_expected_cookies = True
1180 for expected_cookie in query_dict.get('expect', []):
1181 if expected_cookie not in cookies:
1182 got_all_expected_cookies = False
1183 self.send_response(200)
1184 self.send_header('Content-Type', 'text/html')
1185 if got_all_expected_cookies:
1186 for cookie_value in query_dict.get('set', []):
1187 self.send_header('Set-Cookie', '%s' % cookie_value)
1188 self.end_headers()
1189 for data_value in query_dict.get('data', []):
1190 self.wfile.write(data_value)
1191 return True
1192
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001193 def SetHeaderHandler(self):
1194 """This handler sets a response header. Parameters are in the
1195 key%3A%20value&key2%3A%20value2 format."""
1196
1197 if not self._ShouldHandleRequest("/set-header"):
1198 return False
1199
1200 query_char = self.path.find('?')
1201 if query_char != -1:
1202 headers_values = self.path[query_char + 1:].split('&')
1203 else:
1204 headers_values = ("",)
1205 self.send_response(200)
1206 self.send_header('Content-Type', 'text/html')
1207 for header_value in headers_values:
1208 header_value = urllib.unquote(header_value)
1209 (key, value) = header_value.split(': ', 1)
1210 self.send_header(key, value)
1211 self.end_headers()
1212 for header_value in headers_values:
1213 self.wfile.write('%s' % header_value)
1214 return True
1215
initial.commit94958cf2008-07-26 22:42:52 +00001216 def AuthBasicHandler(self):
1217 """This handler tests 'Basic' authentication. It just sends a page with
1218 title 'user/pass' if you succeed."""
1219
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001220 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001221 return False
1222
1223 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001224 expected_password = 'secret'
1225 realm = 'testrealm'
1226 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001227
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001228 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1229 query_params = cgi.parse_qs(query, True)
1230 if 'set-cookie-if-challenged' in query_params:
1231 set_cookie_if_challenged = True
1232 if 'password' in query_params:
1233 expected_password = query_params['password'][0]
1234 if 'realm' in query_params:
1235 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001236
initial.commit94958cf2008-07-26 22:42:52 +00001237 auth = self.headers.getheader('authorization')
1238 try:
1239 if not auth:
1240 raise Exception('no auth')
1241 b64str = re.findall(r'Basic (\S+)', auth)[0]
1242 userpass = base64.b64decode(b64str)
1243 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001244 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001245 raise Exception('wrong password')
1246 except Exception, e:
1247 # Authentication failed.
1248 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001249 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001250 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001251 if set_cookie_if_challenged:
1252 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001253 self.end_headers()
1254 self.wfile.write('<html><head>')
1255 self.wfile.write('<title>Denied: %s</title>' % e)
1256 self.wfile.write('</head><body>')
1257 self.wfile.write('auth=%s<p>' % auth)
1258 self.wfile.write('b64str=%s<p>' % b64str)
1259 self.wfile.write('username: %s<p>' % username)
1260 self.wfile.write('userpass: %s<p>' % userpass)
1261 self.wfile.write('password: %s<p>' % password)
1262 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1263 self.wfile.write('</body></html>')
1264 return True
1265
1266 # Authentication successful. (Return a cachable response to allow for
1267 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001268 old_protocol_version = self.protocol_version
1269 self.protocol_version = "HTTP/1.1"
1270
initial.commit94958cf2008-07-26 22:42:52 +00001271 if_none_match = self.headers.getheader('if-none-match')
1272 if if_none_match == "abc":
1273 self.send_response(304)
1274 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001275 elif url_path.endswith(".gif"):
1276 # Using chrome/test/data/google/logo.gif as the test image
1277 test_image_path = ['google', 'logo.gif']
1278 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1279 if not os.path.isfile(gif_path):
1280 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001281 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001282 return True
1283
1284 f = open(gif_path, "rb")
1285 data = f.read()
1286 f.close()
1287
1288 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001289 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001290 self.send_header('Cache-control', 'max-age=60000')
1291 self.send_header('Etag', 'abc')
1292 self.end_headers()
1293 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001294 else:
1295 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001296 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001297 self.send_header('Cache-control', 'max-age=60000')
1298 self.send_header('Etag', 'abc')
1299 self.end_headers()
1300 self.wfile.write('<html><head>')
1301 self.wfile.write('<title>%s/%s</title>' % (username, password))
1302 self.wfile.write('</head><body>')
1303 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001304 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001305 self.wfile.write('</body></html>')
1306
rvargas@google.com54453b72011-05-19 01:11:11 +00001307 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001308 return True
1309
tonyg@chromium.org75054202010-03-31 22:06:10 +00001310 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001311 """Returns a nonce that's stable per request path for the server's lifetime.
1312 This is a fake implementation. A real implementation would only use a given
1313 nonce a single time (hence the name n-once). However, for the purposes of
1314 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001315
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001316 Args:
1317 force_reset: Iff set, the nonce will be changed. Useful for testing the
1318 "stale" response.
1319 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001320
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001321 if force_reset or not self.server.nonce_time:
1322 self.server.nonce_time = time.time()
1323 return hashlib.md5('privatekey%s%d' %
1324 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001325
1326 def AuthDigestHandler(self):
1327 """This handler tests 'Digest' authentication.
1328
1329 It just sends a page with title 'user/pass' if you succeed.
1330
1331 A stale response is sent iff "stale" is present in the request path.
1332 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001333
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001334 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001335 return False
1336
tonyg@chromium.org75054202010-03-31 22:06:10 +00001337 stale = 'stale' in self.path
1338 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001339 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001340 password = 'secret'
1341 realm = 'testrealm'
1342
1343 auth = self.headers.getheader('authorization')
1344 pairs = {}
1345 try:
1346 if not auth:
1347 raise Exception('no auth')
1348 if not auth.startswith('Digest'):
1349 raise Exception('not digest')
1350 # Pull out all the name="value" pairs as a dictionary.
1351 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1352
1353 # Make sure it's all valid.
1354 if pairs['nonce'] != nonce:
1355 raise Exception('wrong nonce')
1356 if pairs['opaque'] != opaque:
1357 raise Exception('wrong opaque')
1358
1359 # Check the 'response' value and make sure it matches our magic hash.
1360 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001361 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001362 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001363 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001364 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001365 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001366 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1367 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001368 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001369
1370 if pairs['response'] != response:
1371 raise Exception('wrong password')
1372 except Exception, e:
1373 # Authentication failed.
1374 self.send_response(401)
1375 hdr = ('Digest '
1376 'realm="%s", '
1377 'domain="/", '
1378 'qop="auth", '
1379 'algorithm=MD5, '
1380 'nonce="%s", '
1381 'opaque="%s"') % (realm, nonce, opaque)
1382 if stale:
1383 hdr += ', stale="TRUE"'
1384 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001385 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001386 self.end_headers()
1387 self.wfile.write('<html><head>')
1388 self.wfile.write('<title>Denied: %s</title>' % e)
1389 self.wfile.write('</head><body>')
1390 self.wfile.write('auth=%s<p>' % auth)
1391 self.wfile.write('pairs=%s<p>' % pairs)
1392 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1393 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1394 self.wfile.write('</body></html>')
1395 return True
1396
1397 # Authentication successful.
1398 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001399 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001400 self.end_headers()
1401 self.wfile.write('<html><head>')
1402 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1403 self.wfile.write('</head><body>')
1404 self.wfile.write('auth=%s<p>' % auth)
1405 self.wfile.write('pairs=%s<p>' % pairs)
1406 self.wfile.write('</body></html>')
1407
1408 return True
1409
1410 def SlowServerHandler(self):
1411 """Wait for the user suggested time before responding. The syntax is
1412 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001413
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001414 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001415 return False
1416 query_char = self.path.find('?')
1417 wait_sec = 1.0
1418 if query_char >= 0:
1419 try:
1420 wait_sec = int(self.path[query_char + 1:])
1421 except ValueError:
1422 pass
1423 time.sleep(wait_sec)
1424 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001425 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001426 self.end_headers()
1427 self.wfile.write("waited %d seconds" % wait_sec)
1428 return True
1429
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001430 def ChunkedServerHandler(self):
1431 """Send chunked response. Allows to specify chunks parameters:
1432 - waitBeforeHeaders - ms to wait before sending headers
1433 - waitBetweenChunks - ms to wait between chunks
1434 - chunkSize - size of each chunk in bytes
1435 - chunksNumber - number of chunks
1436 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1437 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001438
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001439 if not self._ShouldHandleRequest("/chunked"):
1440 return False
1441 query_char = self.path.find('?')
1442 chunkedSettings = {'waitBeforeHeaders' : 0,
1443 'waitBetweenChunks' : 0,
1444 'chunkSize' : 5,
1445 'chunksNumber' : 5}
1446 if query_char >= 0:
1447 params = self.path[query_char + 1:].split('&')
1448 for param in params:
1449 keyValue = param.split('=')
1450 if len(keyValue) == 2:
1451 try:
1452 chunkedSettings[keyValue[0]] = int(keyValue[1])
1453 except ValueError:
1454 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001455 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001456 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1457 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001458 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001459 self.send_header('Connection', 'close')
1460 self.send_header('Transfer-Encoding', 'chunked')
1461 self.end_headers()
1462 # Chunked encoding: sending all chunks, then final zero-length chunk and
1463 # then final CRLF.
1464 for i in range(0, chunkedSettings['chunksNumber']):
1465 if i > 0:
1466 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1467 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001468 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001469 self.sendChunkHelp('')
1470 return True
1471
initial.commit94958cf2008-07-26 22:42:52 +00001472 def ContentTypeHandler(self):
1473 """Returns a string of html with the given content type. E.g.,
1474 /contenttype?text/css returns an html file with the Content-Type
1475 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001476
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001477 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001478 return False
1479 query_char = self.path.find('?')
1480 content_type = self.path[query_char + 1:].strip()
1481 if not content_type:
1482 content_type = 'text/html'
1483 self.send_response(200)
1484 self.send_header('Content-Type', content_type)
1485 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001486 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001487 return True
1488
creis@google.com2f4f6a42011-03-25 19:44:19 +00001489 def NoContentHandler(self):
1490 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001491
creis@google.com2f4f6a42011-03-25 19:44:19 +00001492 if not self._ShouldHandleRequest("/nocontent"):
1493 return False
1494 self.send_response(204)
1495 self.end_headers()
1496 return True
1497
initial.commit94958cf2008-07-26 22:42:52 +00001498 def ServerRedirectHandler(self):
1499 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001500 '/server-redirect?http://foo.bar/asdf' to redirect to
1501 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001502
1503 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001504 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001505 return False
1506
1507 query_char = self.path.find('?')
1508 if query_char < 0 or len(self.path) <= query_char + 1:
1509 self.sendRedirectHelp(test_name)
1510 return True
1511 dest = self.path[query_char + 1:]
1512
1513 self.send_response(301) # moved permanently
1514 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001516 self.end_headers()
1517 self.wfile.write('<html><head>')
1518 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1519
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001520 return True
initial.commit94958cf2008-07-26 22:42:52 +00001521
1522 def ClientRedirectHandler(self):
1523 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001524 '/client-redirect?http://foo.bar/asdf' to redirect to
1525 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001526
1527 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001528 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001529 return False
1530
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001531 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001532 if query_char < 0 or len(self.path) <= query_char + 1:
1533 self.sendRedirectHelp(test_name)
1534 return True
1535 dest = self.path[query_char + 1:]
1536
1537 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001538 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001539 self.end_headers()
1540 self.wfile.write('<html><head>')
1541 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1542 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1543
1544 return True
1545
tony@chromium.org03266982010-03-05 03:18:42 +00001546 def MultipartHandler(self):
1547 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001548
tony@chromium.org4cb88302011-09-27 22:13:49 +00001549 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001550 if not self._ShouldHandleRequest(test_name):
1551 return False
1552
1553 num_frames = 10
1554 bound = '12345'
1555 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001556 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001557 'multipart/x-mixed-replace;boundary=' + bound)
1558 self.end_headers()
1559
1560 for i in xrange(num_frames):
1561 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001562 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001563 self.wfile.write('<title>page ' + str(i) + '</title>')
1564 self.wfile.write('page ' + str(i))
1565
1566 self.wfile.write('--' + bound + '--')
1567 return True
1568
tony@chromium.org4cb88302011-09-27 22:13:49 +00001569 def MultipartSlowHandler(self):
1570 """Send a multipart response (3 text/html pages) with a slight delay
1571 between each page. This is similar to how some pages show status using
1572 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001573
tony@chromium.org4cb88302011-09-27 22:13:49 +00001574 test_name = '/multipart-slow'
1575 if not self._ShouldHandleRequest(test_name):
1576 return False
1577
1578 num_frames = 3
1579 bound = '12345'
1580 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001581 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001582 'multipart/x-mixed-replace;boundary=' + bound)
1583 self.end_headers()
1584
1585 for i in xrange(num_frames):
1586 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001587 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001588 time.sleep(0.25)
1589 if i == 2:
1590 self.wfile.write('<title>PASS</title>')
1591 else:
1592 self.wfile.write('<title>page ' + str(i) + '</title>')
1593 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1594
1595 self.wfile.write('--' + bound + '--')
1596 return True
1597
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001598 def GetSSLSessionCacheHandler(self):
1599 """Send a reply containing a log of the session cache operations."""
1600
1601 if not self._ShouldHandleRequest('/ssl-session-cache'):
1602 return False
1603
1604 self.send_response(200)
1605 self.send_header('Content-Type', 'text/plain')
1606 self.end_headers()
1607 try:
1608 for (action, sessionID) in self.server.session_cache.log:
1609 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001610 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001611 self.wfile.write('Pass --https-record-resume in order to use' +
1612 ' this request')
1613 return True
1614
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001615 def CloseSocketHandler(self):
1616 """Closes the socket without sending anything."""
1617
1618 if not self._ShouldHandleRequest('/close-socket'):
1619 return False
1620
1621 self.wfile.close()
1622 return True
1623
initial.commit94958cf2008-07-26 22:42:52 +00001624 def DefaultResponseHandler(self):
1625 """This is the catch-all response handler for requests that aren't handled
1626 by one of the special handlers above.
1627 Note that we specify the content-length as without it the https connection
1628 is not closed properly (and the browser keeps expecting data)."""
1629
1630 contents = "Default response given for path: " + self.path
1631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001632 self.send_header('Content-Type', 'text/html')
1633 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001634 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001635 if (self.command != 'HEAD'):
1636 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001637 return True
1638
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001639 def RedirectConnectHandler(self):
1640 """Sends a redirect to the CONNECT request for www.redirect.com. This
1641 response is not specified by the RFC, so the browser should not follow
1642 the redirect."""
1643
1644 if (self.path.find("www.redirect.com") < 0):
1645 return False
1646
1647 dest = "http://www.destination.com/foo.js"
1648
1649 self.send_response(302) # moved temporarily
1650 self.send_header('Location', dest)
1651 self.send_header('Connection', 'close')
1652 self.end_headers()
1653 return True
1654
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001655 def ServerAuthConnectHandler(self):
1656 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1657 response doesn't make sense because the proxy server cannot request
1658 server authentication."""
1659
1660 if (self.path.find("www.server-auth.com") < 0):
1661 return False
1662
1663 challenge = 'Basic realm="WallyWorld"'
1664
1665 self.send_response(401) # unauthorized
1666 self.send_header('WWW-Authenticate', challenge)
1667 self.send_header('Connection', 'close')
1668 self.end_headers()
1669 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001670
1671 def DefaultConnectResponseHandler(self):
1672 """This is the catch-all response handler for CONNECT requests that aren't
1673 handled by one of the special handlers above. Real Web servers respond
1674 with 400 to CONNECT requests."""
1675
1676 contents = "Your client has issued a malformed or illegal request."
1677 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001678 self.send_header('Content-Type', 'text/html')
1679 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001680 self.end_headers()
1681 self.wfile.write(contents)
1682 return True
1683
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001684 def DeviceManagementHandler(self):
1685 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001686
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001687 if not self._ShouldHandleRequest("/device_management"):
1688 return False
1689
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001690 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001691
1692 if not self.server._device_management_handler:
1693 import device_management
1694 policy_path = os.path.join(self.server.data_dir, 'device_management')
1695 self.server._device_management_handler = (
joaodasilva@chromium.org3c069da2012-11-20 16:17:15 +00001696 device_management.TestServer(policy_path, self.server.policy_keys))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001697
1698 http_response, raw_reply = (
1699 self.server._device_management_handler.HandleRequest(self.path,
1700 self.headers,
1701 raw_request))
1702 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001703 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001704 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001705 self.end_headers()
1706 self.wfile.write(raw_reply)
1707 return True
1708
initial.commit94958cf2008-07-26 22:42:52 +00001709 # called by the redirect handling function when there is no parameter
1710 def sendRedirectHelp(self, redirect_name):
1711 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001712 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001713 self.end_headers()
1714 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1715 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1716 self.wfile.write('</body></html>')
1717
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001718 # called by chunked handling function
1719 def sendChunkHelp(self, chunk):
1720 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1721 self.wfile.write('%X\r\n' % len(chunk))
1722 self.wfile.write(chunk)
1723 self.wfile.write('\r\n')
1724
akalin@chromium.org154bb132010-11-12 02:20:27 +00001725
1726class SyncPageHandler(BasePageHandler):
1727 """Handler for the main HTTP sync server."""
1728
1729 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001730 get_handlers = [self.ChromiumSyncTimeHandler,
1731 self.ChromiumSyncMigrationOpHandler,
1732 self.ChromiumSyncCredHandler,
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001733 self.ChromiumSyncXmppCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001734 self.ChromiumSyncDisableNotificationsOpHandler,
1735 self.ChromiumSyncEnableNotificationsOpHandler,
1736 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001737 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001738 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001739 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001740 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001741 self.ChromiumSyncCreateSyncedBookmarksOpHandler,
zea@chromium.org261440b2012-12-19 01:56:52 +00001742 self.ChromiumSyncEnableKeystoreEncryptionOpHandler,
1743 self.ChromiumSyncRotateKeystoreKeysOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001744
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001745 post_handlers = [self.ChromiumSyncCommandHandler,
1746 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001747 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001748 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001749 post_handlers, [])
1750
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001751
akalin@chromium.org154bb132010-11-12 02:20:27 +00001752 def ChromiumSyncTimeHandler(self):
1753 """Handle Chromium sync .../time requests.
1754
1755 The syncer sometimes checks server reachability by examining /time.
1756 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001757
akalin@chromium.org154bb132010-11-12 02:20:27 +00001758 test_name = "/chromiumsync/time"
1759 if not self._ShouldHandleRequest(test_name):
1760 return False
1761
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001762 # Chrome hates it if we send a response before reading the request.
1763 if self.headers.getheader('content-length'):
1764 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001765 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001766
akalin@chromium.org154bb132010-11-12 02:20:27 +00001767 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001768 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001769 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001770 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001771 return True
1772
1773 def ChromiumSyncCommandHandler(self):
1774 """Handle a chromiumsync command arriving via http.
1775
1776 This covers all sync protocol commands: authentication, getupdates, and
1777 commit.
1778 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001779
akalin@chromium.org154bb132010-11-12 02:20:27 +00001780 test_name = "/chromiumsync/command"
1781 if not self._ShouldHandleRequest(test_name):
1782 return False
1783
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001784 length = int(self.headers.getheader('content-length'))
1785 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001786 http_response = 200
1787 raw_reply = None
1788 if not self.server.GetAuthenticated():
1789 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001790 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1791 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001792 else:
1793 http_response, raw_reply = self.server.HandleCommand(
1794 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001795
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001796 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001797 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001798 if http_response == 401:
1799 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001800 self.end_headers()
1801 self.wfile.write(raw_reply)
1802 return True
1803
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001804 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001805 test_name = "/chromiumsync/migrate"
1806 if not self._ShouldHandleRequest(test_name):
1807 return False
1808
1809 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1810 self.path)
1811 self.send_response(http_response)
1812 self.send_header('Content-Type', 'text/html')
1813 self.send_header('Content-Length', len(raw_reply))
1814 self.end_headers()
1815 self.wfile.write(raw_reply)
1816 return True
1817
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001818 def ChromiumSyncCredHandler(self):
1819 test_name = "/chromiumsync/cred"
1820 if not self._ShouldHandleRequest(test_name):
1821 return False
1822 try:
1823 query = urlparse.urlparse(self.path)[4]
1824 cred_valid = urlparse.parse_qs(query)['valid']
1825 if cred_valid[0] == 'True':
1826 self.server.SetAuthenticated(True)
1827 else:
1828 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001829 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001830 self.server.SetAuthenticated(False)
1831
1832 http_response = 200
1833 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1834 self.send_response(http_response)
1835 self.send_header('Content-Type', 'text/html')
1836 self.send_header('Content-Length', len(raw_reply))
1837 self.end_headers()
1838 self.wfile.write(raw_reply)
1839 return True
1840
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001841 def ChromiumSyncXmppCredHandler(self):
1842 test_name = "/chromiumsync/xmppcred"
1843 if not self._ShouldHandleRequest(test_name):
1844 return False
1845 xmpp_server = self.server.GetXmppServer()
1846 try:
1847 query = urlparse.urlparse(self.path)[4]
1848 cred_valid = urlparse.parse_qs(query)['valid']
1849 if cred_valid[0] == 'True':
1850 xmpp_server.SetAuthenticated(True)
1851 else:
1852 xmpp_server.SetAuthenticated(False)
1853 except:
1854 xmpp_server.SetAuthenticated(False)
1855
1856 http_response = 200
1857 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated()
1858 self.send_response(http_response)
1859 self.send_header('Content-Type', 'text/html')
1860 self.send_header('Content-Length', len(raw_reply))
1861 self.end_headers()
1862 self.wfile.write(raw_reply)
1863 return True
1864
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001865 def ChromiumSyncDisableNotificationsOpHandler(self):
1866 test_name = "/chromiumsync/disablenotifications"
1867 if not self._ShouldHandleRequest(test_name):
1868 return False
1869 self.server.GetXmppServer().DisableNotifications()
1870 result = 200
1871 raw_reply = ('<html><title>Notifications disabled</title>'
1872 '<H1>Notifications disabled</H1></html>')
1873 self.send_response(result)
1874 self.send_header('Content-Type', 'text/html')
1875 self.send_header('Content-Length', len(raw_reply))
1876 self.end_headers()
1877 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001878 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001879
1880 def ChromiumSyncEnableNotificationsOpHandler(self):
1881 test_name = "/chromiumsync/enablenotifications"
1882 if not self._ShouldHandleRequest(test_name):
1883 return False
1884 self.server.GetXmppServer().EnableNotifications()
1885 result = 200
1886 raw_reply = ('<html><title>Notifications enabled</title>'
1887 '<H1>Notifications enabled</H1></html>')
1888 self.send_response(result)
1889 self.send_header('Content-Type', 'text/html')
1890 self.send_header('Content-Length', len(raw_reply))
1891 self.end_headers()
1892 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001893 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001894
1895 def ChromiumSyncSendNotificationOpHandler(self):
1896 test_name = "/chromiumsync/sendnotification"
1897 if not self._ShouldHandleRequest(test_name):
1898 return False
1899 query = urlparse.urlparse(self.path)[4]
1900 query_params = urlparse.parse_qs(query)
1901 channel = ''
1902 data = ''
1903 if 'channel' in query_params:
1904 channel = query_params['channel'][0]
1905 if 'data' in query_params:
1906 data = query_params['data'][0]
1907 self.server.GetXmppServer().SendNotification(channel, data)
1908 result = 200
1909 raw_reply = ('<html><title>Notification sent</title>'
1910 '<H1>Notification sent with channel "%s" '
1911 'and data "%s"</H1></html>'
1912 % (channel, data))
1913 self.send_response(result)
1914 self.send_header('Content-Type', 'text/html')
1915 self.send_header('Content-Length', len(raw_reply))
1916 self.end_headers()
1917 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001918 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001919
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001920 def ChromiumSyncBirthdayErrorOpHandler(self):
1921 test_name = "/chromiumsync/birthdayerror"
1922 if not self._ShouldHandleRequest(test_name):
1923 return False
1924 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1925 self.send_response(result)
1926 self.send_header('Content-Type', 'text/html')
1927 self.send_header('Content-Length', len(raw_reply))
1928 self.end_headers()
1929 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001930 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001931
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001932 def ChromiumSyncTransientErrorOpHandler(self):
1933 test_name = "/chromiumsync/transienterror"
1934 if not self._ShouldHandleRequest(test_name):
1935 return False
1936 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1937 self.send_response(result)
1938 self.send_header('Content-Type', 'text/html')
1939 self.send_header('Content-Length', len(raw_reply))
1940 self.end_headers()
1941 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001942 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001943
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001944 def ChromiumSyncErrorOpHandler(self):
1945 test_name = "/chromiumsync/error"
1946 if not self._ShouldHandleRequest(test_name):
1947 return False
1948 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1949 self.path)
1950 self.send_response(result)
1951 self.send_header('Content-Type', 'text/html')
1952 self.send_header('Content-Length', len(raw_reply))
1953 self.end_headers()
1954 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001955 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001956
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001957 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1958 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001959 if not self._ShouldHandleRequest(test_name):
1960 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001961 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001962 self.send_response(result)
1963 self.send_header('Content-Type', 'text/html')
1964 self.send_header('Content-Length', len(raw_reply))
1965 self.end_headers()
1966 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001967 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001968
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001969 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1970 test_name = "/chromiumsync/createsyncedbookmarks"
1971 if not self._ShouldHandleRequest(test_name):
1972 return False
1973 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1974 self.send_response(result)
1975 self.send_header('Content-Type', 'text/html')
1976 self.send_header('Content-Length', len(raw_reply))
1977 self.end_headers()
1978 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001979 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001980
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001981 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
1982 test_name = "/chromiumsync/enablekeystoreencryption"
1983 if not self._ShouldHandleRequest(test_name):
1984 return False
1985 result, raw_reply = (
1986 self.server._sync_handler.HandleEnableKeystoreEncryption())
1987 self.send_response(result)
1988 self.send_header('Content-Type', 'text/html')
1989 self.send_header('Content-Length', len(raw_reply))
1990 self.end_headers()
1991 self.wfile.write(raw_reply)
1992 return True
1993
zea@chromium.org261440b2012-12-19 01:56:52 +00001994 def ChromiumSyncRotateKeystoreKeysOpHandler(self):
1995 test_name = "/chromiumsync/rotatekeystorekeys"
1996 if not self._ShouldHandleRequest(test_name):
1997 return False
1998 result, raw_reply = (
1999 self.server._sync_handler.HandleRotateKeystoreKeys())
2000 self.send_response(result)
2001 self.send_header('Content-Type', 'text/html')
2002 self.send_header('Content-Length', len(raw_reply))
2003 self.end_headers()
2004 self.wfile.write(raw_reply)
2005 return True
2006
akalin@chromium.org154bb132010-11-12 02:20:27 +00002007
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002008class OCSPHandler(BasePageHandler):
2009 def __init__(self, request, client_address, socket_server):
2010 handlers = [self.OCSPResponse]
2011 self.ocsp_response = socket_server.ocsp_response
2012 BasePageHandler.__init__(self, request, client_address, socket_server,
2013 [], handlers, [], handlers, [])
2014
2015 def OCSPResponse(self):
2016 self.send_response(200)
2017 self.send_header('Content-Type', 'application/ocsp-response')
2018 self.send_header('Content-Length', str(len(self.ocsp_response)))
2019 self.end_headers()
2020
2021 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002022
mattm@chromium.org830a3712012-11-07 23:00:07 +00002023
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002024class TCPEchoHandler(SocketServer.BaseRequestHandler):
2025 """The RequestHandler class for TCP echo server.
2026
2027 It is instantiated once per connection to the server, and overrides the
2028 handle() method to implement communication to the client.
2029 """
2030
2031 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002032 """Handles the request from the client and constructs a response."""
2033
2034 data = self.request.recv(65536).strip()
2035 # Verify the "echo request" message received from the client. Send back
2036 # "echo response" message if "echo request" message is valid.
2037 try:
2038 return_data = echo_message.GetEchoResponseData(data)
2039 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002040 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002041 except ValueError:
2042 return
2043
2044 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002045
2046
2047class UDPEchoHandler(SocketServer.BaseRequestHandler):
2048 """The RequestHandler class for UDP echo server.
2049
2050 It is instantiated once per connection to the server, and overrides the
2051 handle() method to implement communication to the client.
2052 """
2053
2054 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002055 """Handles the request from the client and constructs a response."""
2056
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002057 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002058 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002059 # Verify the "echo request" message received from the client. Send back
2060 # "echo response" message if "echo request" message is valid.
2061 try:
2062 return_data = echo_message.GetEchoResponseData(data)
2063 if not return_data:
2064 return
2065 except ValueError:
2066 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002067 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002068
2069
bashi@chromium.org33233532012-09-08 17:37:24 +00002070class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2071 """A request handler that behaves as a proxy server which requires
2072 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2073 """
2074
2075 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2076
2077 def parse_request(self):
2078 """Overrides parse_request to check credential."""
2079
2080 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2081 return False
2082
2083 auth = self.headers.getheader('Proxy-Authorization')
2084 if auth != self._AUTH_CREDENTIAL:
2085 self.send_response(407)
2086 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2087 self.end_headers()
2088 return False
2089
2090 return True
2091
2092 def _start_read_write(self, sock):
2093 sock.setblocking(0)
2094 self.request.setblocking(0)
2095 rlist = [self.request, sock]
2096 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002097 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002098 if errors:
2099 self.send_response(500)
2100 self.end_headers()
2101 return
2102 for s in ready_sockets:
2103 received = s.recv(1024)
2104 if len(received) == 0:
2105 return
2106 if s == self.request:
2107 other = sock
2108 else:
2109 other = self.request
2110 other.send(received)
2111
2112 def _do_common_method(self):
2113 url = urlparse.urlparse(self.path)
2114 port = url.port
2115 if not port:
2116 if url.scheme == 'http':
2117 port = 80
2118 elif url.scheme == 'https':
2119 port = 443
2120 if not url.hostname or not port:
2121 self.send_response(400)
2122 self.end_headers()
2123 return
2124
2125 if len(url.path) == 0:
2126 path = '/'
2127 else:
2128 path = url.path
2129 if len(url.query) > 0:
2130 path = '%s?%s' % (url.path, url.query)
2131
2132 sock = None
2133 try:
2134 sock = socket.create_connection((url.hostname, port))
2135 sock.send('%s %s %s\r\n' % (
2136 self.command, path, self.protocol_version))
2137 for header in self.headers.headers:
2138 header = header.strip()
2139 if (header.lower().startswith('connection') or
2140 header.lower().startswith('proxy')):
2141 continue
2142 sock.send('%s\r\n' % header)
2143 sock.send('\r\n')
2144 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002145 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002146 self.send_response(500)
2147 self.end_headers()
2148 finally:
2149 if sock is not None:
2150 sock.close()
2151
2152 def do_CONNECT(self):
2153 try:
2154 pos = self.path.rfind(':')
2155 host = self.path[:pos]
2156 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002157 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002158 self.send_response(400)
2159 self.end_headers()
2160
2161 try:
2162 sock = socket.create_connection((host, port))
2163 self.send_response(200, 'Connection established')
2164 self.end_headers()
2165 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002166 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002167 self.send_response(500)
2168 self.end_headers()
2169 finally:
2170 sock.close()
2171
2172 def do_GET(self):
2173 self._do_common_method()
2174
2175 def do_HEAD(self):
2176 self._do_common_method()
2177
2178
mattm@chromium.org830a3712012-11-07 23:00:07 +00002179class ServerRunner(testserver_base.TestServerRunner):
2180 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002181
mattm@chromium.org830a3712012-11-07 23:00:07 +00002182 def __init__(self):
2183 super(ServerRunner, self).__init__()
2184 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002185
mattm@chromium.org830a3712012-11-07 23:00:07 +00002186 def __make_data_dir(self):
2187 if self.options.data_dir:
2188 if not os.path.isdir(self.options.data_dir):
2189 raise testserver_base.OptionError('specified data dir not found: ' +
2190 self.options.data_dir + ' exiting...')
2191 my_data_dir = self.options.data_dir
2192 else:
2193 # Create the default path to our data dir, relative to the exe dir.
2194 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2195 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002196
mattm@chromium.org830a3712012-11-07 23:00:07 +00002197 #TODO(ibrar): Must use Find* funtion defined in google\tools
2198 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002199
mattm@chromium.org830a3712012-11-07 23:00:07 +00002200 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002201
mattm@chromium.org830a3712012-11-07 23:00:07 +00002202 def create_server(self, server_data):
2203 port = self.options.port
2204 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002205
mattm@chromium.org830a3712012-11-07 23:00:07 +00002206 if self.options.server_type == SERVER_HTTP:
2207 if self.options.https:
2208 pem_cert_and_key = None
2209 if self.options.cert_and_key_file:
2210 if not os.path.isfile(self.options.cert_and_key_file):
2211 raise testserver_base.OptionError(
2212 'specified server cert file not found: ' +
2213 self.options.cert_and_key_file + ' exiting...')
2214 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002215 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002216 # generate a new certificate and run an OCSP server for it.
2217 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2218 print ('OCSP server started on %s:%d...' %
2219 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002220
mattm@chromium.org830a3712012-11-07 23:00:07 +00002221 ocsp_der = None
2222 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002223
mattm@chromium.org830a3712012-11-07 23:00:07 +00002224 if self.options.ocsp == 'ok':
2225 ocsp_state = minica.OCSP_STATE_GOOD
2226 elif self.options.ocsp == 'revoked':
2227 ocsp_state = minica.OCSP_STATE_REVOKED
2228 elif self.options.ocsp == 'invalid':
2229 ocsp_state = minica.OCSP_STATE_INVALID
2230 elif self.options.ocsp == 'unauthorized':
2231 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2232 elif self.options.ocsp == 'unknown':
2233 ocsp_state = minica.OCSP_STATE_UNKNOWN
2234 else:
2235 raise testserver_base.OptionError('unknown OCSP status: ' +
2236 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002237
mattm@chromium.org830a3712012-11-07 23:00:07 +00002238 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2239 subject = "127.0.0.1",
2240 ocsp_url = ("http://%s:%d/ocsp" %
2241 (host, self.__ocsp_server.server_port)),
2242 ocsp_state = ocsp_state)
2243
2244 self.__ocsp_server.ocsp_response = ocsp_der
2245
2246 for ca_cert in self.options.ssl_client_ca:
2247 if not os.path.isfile(ca_cert):
2248 raise testserver_base.OptionError(
2249 'specified trusted client CA file not found: ' + ca_cert +
2250 ' exiting...')
2251 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2252 self.options.ssl_client_auth,
2253 self.options.ssl_client_ca,
2254 self.options.ssl_bulk_cipher,
2255 self.options.record_resume,
2256 self.options.tls_intolerant)
2257 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2258 else:
2259 server = HTTPServer((host, port), TestPageHandler)
2260 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2261
2262 server.data_dir = self.__make_data_dir()
2263 server.file_root_url = self.options.file_root_url
2264 server_data['port'] = server.server_port
2265 server._device_management_handler = None
2266 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00002267 elif self.options.server_type == SERVER_WEBSOCKET:
2268 # Launch pywebsocket via WebSocketServer.
2269 logger = logging.getLogger()
2270 logger.addHandler(logging.StreamHandler())
2271 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2272 # is required to work correctly. It should be fixed from pywebsocket side.
2273 os.chdir(self.__make_data_dir())
2274 websocket_options = WebSocketOptions(host, port, '.')
2275 if self.options.cert_and_key_file:
2276 websocket_options.use_tls = True
2277 websocket_options.private_key = self.options.cert_and_key_file
2278 websocket_options.certificate = self.options.cert_and_key_file
2279 if self.options.ssl_client_auth:
2280 websocket_options.tls_client_auth = True
2281 if len(self.options.ssl_client_ca) != 1:
2282 raise testserver_base.OptionError(
2283 'one trusted client CA file should be specified')
2284 if not os.path.isfile(self.options.ssl_client_ca[0]):
2285 raise testserver_base.OptionError(
2286 'specified trusted client CA file not found: ' +
2287 self.options.ssl_client_ca[0] + ' exiting...')
2288 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2289 server = WebSocketServer(websocket_options)
2290 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2291 server_data['port'] = server.server_port
2292 elif self.options.server_type == SERVER_SYNC:
2293 xmpp_port = self.options.xmpp_port
2294 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2295 print 'Sync HTTP server started on port %d...' % server.server_port
2296 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2297 server_data['port'] = server.server_port
2298 server_data['xmpp_port'] = server.xmpp_port
2299 elif self.options.server_type == SERVER_TCP_ECHO:
2300 # Used for generating the key (randomly) that encodes the "echo request"
2301 # message.
2302 random.seed()
2303 server = TCPEchoServer((host, port), TCPEchoHandler)
2304 print 'Echo TCP server started on port %d...' % server.server_port
2305 server_data['port'] = server.server_port
2306 elif self.options.server_type == SERVER_UDP_ECHO:
2307 # Used for generating the key (randomly) that encodes the "echo request"
2308 # message.
2309 random.seed()
2310 server = UDPEchoServer((host, port), UDPEchoHandler)
2311 print 'Echo UDP server started on port %d...' % server.server_port
2312 server_data['port'] = server.server_port
2313 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2314 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2315 print 'BasicAuthProxy server started on port %d...' % server.server_port
2316 server_data['port'] = server.server_port
2317 elif self.options.server_type == SERVER_FTP:
2318 my_data_dir = self.__make_data_dir()
2319
2320 # Instantiate a dummy authorizer for managing 'virtual' users
2321 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2322
2323 # Define a new user having full r/w permissions and a read-only
2324 # anonymous user
2325 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2326
2327 authorizer.add_anonymous(my_data_dir)
2328
2329 # Instantiate FTP handler class
2330 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2331 ftp_handler.authorizer = authorizer
2332
2333 # Define a customized banner (string returned when client connects)
2334 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2335 pyftpdlib.ftpserver.__ver__)
2336
2337 # Instantiate FTP server class and listen to address:port
2338 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2339 server_data['port'] = server.socket.getsockname()[1]
2340 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002341 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002342 raise testserver_base.OptionError('unknown server type' +
2343 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002344
mattm@chromium.org830a3712012-11-07 23:00:07 +00002345 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002346
mattm@chromium.org830a3712012-11-07 23:00:07 +00002347 def run_server(self):
2348 if self.__ocsp_server:
2349 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002350
mattm@chromium.org830a3712012-11-07 23:00:07 +00002351 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002352
mattm@chromium.org830a3712012-11-07 23:00:07 +00002353 if self.__ocsp_server:
2354 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002355
mattm@chromium.org830a3712012-11-07 23:00:07 +00002356 def add_options(self):
2357 testserver_base.TestServerRunner.add_options(self)
2358 self.option_parser.add_option('-f', '--ftp', action='store_const',
2359 const=SERVER_FTP, default=SERVER_HTTP,
2360 dest='server_type',
2361 help='start up an FTP server.')
2362 self.option_parser.add_option('--sync', action='store_const',
2363 const=SERVER_SYNC, default=SERVER_HTTP,
2364 dest='server_type',
2365 help='start up a sync server.')
2366 self.option_parser.add_option('--tcp-echo', action='store_const',
2367 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2368 dest='server_type',
2369 help='start up a tcp echo server.')
2370 self.option_parser.add_option('--udp-echo', action='store_const',
2371 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2372 dest='server_type',
2373 help='start up a udp echo server.')
2374 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2375 const=SERVER_BASIC_AUTH_PROXY,
2376 default=SERVER_HTTP, dest='server_type',
2377 help='start up a proxy server which requires '
2378 'basic authentication.')
2379 self.option_parser.add_option('--websocket', action='store_const',
2380 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2381 dest='server_type',
2382 help='start up a WebSocket server.')
2383 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2384 help='Port used by the XMPP server. If '
2385 'unspecified, the XMPP server will listen on '
2386 'an ephemeral port.')
2387 self.option_parser.add_option('--data-dir', dest='data_dir',
2388 help='Directory from which to read the '
2389 'files.')
2390 self.option_parser.add_option('--https', action='store_true',
2391 dest='https', help='Specify that https '
2392 'should be used.')
2393 self.option_parser.add_option('--cert-and-key-file',
2394 dest='cert_and_key_file', help='specify the '
2395 'path to the file containing the certificate '
2396 'and private key for the server in PEM '
2397 'format')
2398 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2399 help='The type of OCSP response generated '
2400 'for the automatically generated '
2401 'certificate. One of [ok,revoked,invalid]')
2402 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2403 default='0', type='int',
2404 help='If nonzero, certain TLS connections '
2405 'will be aborted in order to test version '
2406 'fallback. 1 means all TLS versions will be '
2407 'aborted. 2 means TLS 1.1 or higher will be '
2408 'aborted. 3 means TLS 1.2 or higher will be '
2409 'aborted.')
2410 self.option_parser.add_option('--https-record-resume',
2411 dest='record_resume', const=True,
2412 default=False, action='store_const',
2413 help='Record resumption cache events rather '
2414 'than resuming as normal. Allows the use of '
2415 'the /ssl-session-cache request')
2416 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2417 help='Require SSL client auth on every '
2418 'connection.')
2419 self.option_parser.add_option('--ssl-client-ca', action='append',
2420 default=[], help='Specify that the client '
2421 'certificate request should include the CA '
2422 'named in the subject of the DER-encoded '
2423 'certificate contained in the specified '
2424 'file. This option may appear multiple '
2425 'times, indicating multiple CA names should '
2426 'be sent in the request.')
2427 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2428 help='Specify the bulk encryption '
2429 'algorithm(s) that will be accepted by the '
2430 'SSL server. Valid values are "aes256", '
2431 '"aes128", "3des", "rc4". If omitted, all '
2432 'algorithms will be used. This option may '
2433 'appear multiple times, indicating '
2434 'multiple algorithms should be enabled.');
2435 self.option_parser.add_option('--file-root-url', default='/files/',
2436 help='Specify a root URL for files served.')
2437 self.option_parser.add_option('--policy-key', action='append',
2438 dest='policy_keys',
2439 help='Specify a path to a PEM-encoded '
2440 'private key to use for policy signing. May '
2441 'be specified multiple times in order to '
2442 'load multipe keys into the server. If the '
2443 'server has multiple keys, it will rotate '
2444 'through them in at each request a '
2445 'round-robin fashion. The server will '
2446 'generate a random key if none is specified '
2447 'on the command line.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002448
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002449
initial.commit94958cf2008-07-26 22:42:52 +00002450if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002451 sys.exit(ServerRunner().main())