blob: 139724c7d47132745fbfa62173ac179a084eb30f [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,
1742 self.ChromiumSyncEnableKeystoreEncryptionOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001743
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001744 post_handlers = [self.ChromiumSyncCommandHandler,
1745 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001746 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001747 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001748 post_handlers, [])
1749
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001750
akalin@chromium.org154bb132010-11-12 02:20:27 +00001751 def ChromiumSyncTimeHandler(self):
1752 """Handle Chromium sync .../time requests.
1753
1754 The syncer sometimes checks server reachability by examining /time.
1755 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001756
akalin@chromium.org154bb132010-11-12 02:20:27 +00001757 test_name = "/chromiumsync/time"
1758 if not self._ShouldHandleRequest(test_name):
1759 return False
1760
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001761 # Chrome hates it if we send a response before reading the request.
1762 if self.headers.getheader('content-length'):
1763 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001764 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001765
akalin@chromium.org154bb132010-11-12 02:20:27 +00001766 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001767 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001768 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001769 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001770 return True
1771
1772 def ChromiumSyncCommandHandler(self):
1773 """Handle a chromiumsync command arriving via http.
1774
1775 This covers all sync protocol commands: authentication, getupdates, and
1776 commit.
1777 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001778
akalin@chromium.org154bb132010-11-12 02:20:27 +00001779 test_name = "/chromiumsync/command"
1780 if not self._ShouldHandleRequest(test_name):
1781 return False
1782
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001783 length = int(self.headers.getheader('content-length'))
1784 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001785 http_response = 200
1786 raw_reply = None
1787 if not self.server.GetAuthenticated():
1788 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001789 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1790 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001791 else:
1792 http_response, raw_reply = self.server.HandleCommand(
1793 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001794
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001795 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001796 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001797 if http_response == 401:
1798 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001799 self.end_headers()
1800 self.wfile.write(raw_reply)
1801 return True
1802
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001803 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001804 test_name = "/chromiumsync/migrate"
1805 if not self._ShouldHandleRequest(test_name):
1806 return False
1807
1808 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1809 self.path)
1810 self.send_response(http_response)
1811 self.send_header('Content-Type', 'text/html')
1812 self.send_header('Content-Length', len(raw_reply))
1813 self.end_headers()
1814 self.wfile.write(raw_reply)
1815 return True
1816
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001817 def ChromiumSyncCredHandler(self):
1818 test_name = "/chromiumsync/cred"
1819 if not self._ShouldHandleRequest(test_name):
1820 return False
1821 try:
1822 query = urlparse.urlparse(self.path)[4]
1823 cred_valid = urlparse.parse_qs(query)['valid']
1824 if cred_valid[0] == 'True':
1825 self.server.SetAuthenticated(True)
1826 else:
1827 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001828 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001829 self.server.SetAuthenticated(False)
1830
1831 http_response = 200
1832 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1833 self.send_response(http_response)
1834 self.send_header('Content-Type', 'text/html')
1835 self.send_header('Content-Length', len(raw_reply))
1836 self.end_headers()
1837 self.wfile.write(raw_reply)
1838 return True
1839
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001840 def ChromiumSyncXmppCredHandler(self):
1841 test_name = "/chromiumsync/xmppcred"
1842 if not self._ShouldHandleRequest(test_name):
1843 return False
1844 xmpp_server = self.server.GetXmppServer()
1845 try:
1846 query = urlparse.urlparse(self.path)[4]
1847 cred_valid = urlparse.parse_qs(query)['valid']
1848 if cred_valid[0] == 'True':
1849 xmpp_server.SetAuthenticated(True)
1850 else:
1851 xmpp_server.SetAuthenticated(False)
1852 except:
1853 xmpp_server.SetAuthenticated(False)
1854
1855 http_response = 200
1856 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated()
1857 self.send_response(http_response)
1858 self.send_header('Content-Type', 'text/html')
1859 self.send_header('Content-Length', len(raw_reply))
1860 self.end_headers()
1861 self.wfile.write(raw_reply)
1862 return True
1863
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001864 def ChromiumSyncDisableNotificationsOpHandler(self):
1865 test_name = "/chromiumsync/disablenotifications"
1866 if not self._ShouldHandleRequest(test_name):
1867 return False
1868 self.server.GetXmppServer().DisableNotifications()
1869 result = 200
1870 raw_reply = ('<html><title>Notifications disabled</title>'
1871 '<H1>Notifications disabled</H1></html>')
1872 self.send_response(result)
1873 self.send_header('Content-Type', 'text/html')
1874 self.send_header('Content-Length', len(raw_reply))
1875 self.end_headers()
1876 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001877 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001878
1879 def ChromiumSyncEnableNotificationsOpHandler(self):
1880 test_name = "/chromiumsync/enablenotifications"
1881 if not self._ShouldHandleRequest(test_name):
1882 return False
1883 self.server.GetXmppServer().EnableNotifications()
1884 result = 200
1885 raw_reply = ('<html><title>Notifications enabled</title>'
1886 '<H1>Notifications enabled</H1></html>')
1887 self.send_response(result)
1888 self.send_header('Content-Type', 'text/html')
1889 self.send_header('Content-Length', len(raw_reply))
1890 self.end_headers()
1891 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001892 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001893
1894 def ChromiumSyncSendNotificationOpHandler(self):
1895 test_name = "/chromiumsync/sendnotification"
1896 if not self._ShouldHandleRequest(test_name):
1897 return False
1898 query = urlparse.urlparse(self.path)[4]
1899 query_params = urlparse.parse_qs(query)
1900 channel = ''
1901 data = ''
1902 if 'channel' in query_params:
1903 channel = query_params['channel'][0]
1904 if 'data' in query_params:
1905 data = query_params['data'][0]
1906 self.server.GetXmppServer().SendNotification(channel, data)
1907 result = 200
1908 raw_reply = ('<html><title>Notification sent</title>'
1909 '<H1>Notification sent with channel "%s" '
1910 'and data "%s"</H1></html>'
1911 % (channel, data))
1912 self.send_response(result)
1913 self.send_header('Content-Type', 'text/html')
1914 self.send_header('Content-Length', len(raw_reply))
1915 self.end_headers()
1916 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001917 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001918
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001919 def ChromiumSyncBirthdayErrorOpHandler(self):
1920 test_name = "/chromiumsync/birthdayerror"
1921 if not self._ShouldHandleRequest(test_name):
1922 return False
1923 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1924 self.send_response(result)
1925 self.send_header('Content-Type', 'text/html')
1926 self.send_header('Content-Length', len(raw_reply))
1927 self.end_headers()
1928 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001929 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001930
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001931 def ChromiumSyncTransientErrorOpHandler(self):
1932 test_name = "/chromiumsync/transienterror"
1933 if not self._ShouldHandleRequest(test_name):
1934 return False
1935 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1936 self.send_response(result)
1937 self.send_header('Content-Type', 'text/html')
1938 self.send_header('Content-Length', len(raw_reply))
1939 self.end_headers()
1940 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001941 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001942
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001943 def ChromiumSyncErrorOpHandler(self):
1944 test_name = "/chromiumsync/error"
1945 if not self._ShouldHandleRequest(test_name):
1946 return False
1947 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1948 self.path)
1949 self.send_response(result)
1950 self.send_header('Content-Type', 'text/html')
1951 self.send_header('Content-Length', len(raw_reply))
1952 self.end_headers()
1953 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001954 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001955
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001956 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1957 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001958 if not self._ShouldHandleRequest(test_name):
1959 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001960 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001961 self.send_response(result)
1962 self.send_header('Content-Type', 'text/html')
1963 self.send_header('Content-Length', len(raw_reply))
1964 self.end_headers()
1965 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001966 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001967
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001968 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1969 test_name = "/chromiumsync/createsyncedbookmarks"
1970 if not self._ShouldHandleRequest(test_name):
1971 return False
1972 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1973 self.send_response(result)
1974 self.send_header('Content-Type', 'text/html')
1975 self.send_header('Content-Length', len(raw_reply))
1976 self.end_headers()
1977 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001978 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001979
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001980 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
1981 test_name = "/chromiumsync/enablekeystoreencryption"
1982 if not self._ShouldHandleRequest(test_name):
1983 return False
1984 result, raw_reply = (
1985 self.server._sync_handler.HandleEnableKeystoreEncryption())
1986 self.send_response(result)
1987 self.send_header('Content-Type', 'text/html')
1988 self.send_header('Content-Length', len(raw_reply))
1989 self.end_headers()
1990 self.wfile.write(raw_reply)
1991 return True
1992
akalin@chromium.org154bb132010-11-12 02:20:27 +00001993
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001994class OCSPHandler(BasePageHandler):
1995 def __init__(self, request, client_address, socket_server):
1996 handlers = [self.OCSPResponse]
1997 self.ocsp_response = socket_server.ocsp_response
1998 BasePageHandler.__init__(self, request, client_address, socket_server,
1999 [], handlers, [], handlers, [])
2000
2001 def OCSPResponse(self):
2002 self.send_response(200)
2003 self.send_header('Content-Type', 'application/ocsp-response')
2004 self.send_header('Content-Length', str(len(self.ocsp_response)))
2005 self.end_headers()
2006
2007 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002008
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002010class TCPEchoHandler(SocketServer.BaseRequestHandler):
2011 """The RequestHandler class for TCP echo server.
2012
2013 It is instantiated once per connection to the server, and overrides the
2014 handle() method to implement communication to the client.
2015 """
2016
2017 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002018 """Handles the request from the client and constructs a response."""
2019
2020 data = self.request.recv(65536).strip()
2021 # Verify the "echo request" message received from the client. Send back
2022 # "echo response" message if "echo request" message is valid.
2023 try:
2024 return_data = echo_message.GetEchoResponseData(data)
2025 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002026 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002027 except ValueError:
2028 return
2029
2030 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002031
2032
2033class UDPEchoHandler(SocketServer.BaseRequestHandler):
2034 """The RequestHandler class for UDP echo server.
2035
2036 It is instantiated once per connection to the server, and overrides the
2037 handle() method to implement communication to the client.
2038 """
2039
2040 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002041 """Handles the request from the client and constructs a response."""
2042
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002043 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002044 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002045 # Verify the "echo request" message received from the client. Send back
2046 # "echo response" message if "echo request" message is valid.
2047 try:
2048 return_data = echo_message.GetEchoResponseData(data)
2049 if not return_data:
2050 return
2051 except ValueError:
2052 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002053 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002054
2055
bashi@chromium.org33233532012-09-08 17:37:24 +00002056class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2057 """A request handler that behaves as a proxy server which requires
2058 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2059 """
2060
2061 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2062
2063 def parse_request(self):
2064 """Overrides parse_request to check credential."""
2065
2066 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2067 return False
2068
2069 auth = self.headers.getheader('Proxy-Authorization')
2070 if auth != self._AUTH_CREDENTIAL:
2071 self.send_response(407)
2072 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2073 self.end_headers()
2074 return False
2075
2076 return True
2077
2078 def _start_read_write(self, sock):
2079 sock.setblocking(0)
2080 self.request.setblocking(0)
2081 rlist = [self.request, sock]
2082 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002083 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002084 if errors:
2085 self.send_response(500)
2086 self.end_headers()
2087 return
2088 for s in ready_sockets:
2089 received = s.recv(1024)
2090 if len(received) == 0:
2091 return
2092 if s == self.request:
2093 other = sock
2094 else:
2095 other = self.request
2096 other.send(received)
2097
2098 def _do_common_method(self):
2099 url = urlparse.urlparse(self.path)
2100 port = url.port
2101 if not port:
2102 if url.scheme == 'http':
2103 port = 80
2104 elif url.scheme == 'https':
2105 port = 443
2106 if not url.hostname or not port:
2107 self.send_response(400)
2108 self.end_headers()
2109 return
2110
2111 if len(url.path) == 0:
2112 path = '/'
2113 else:
2114 path = url.path
2115 if len(url.query) > 0:
2116 path = '%s?%s' % (url.path, url.query)
2117
2118 sock = None
2119 try:
2120 sock = socket.create_connection((url.hostname, port))
2121 sock.send('%s %s %s\r\n' % (
2122 self.command, path, self.protocol_version))
2123 for header in self.headers.headers:
2124 header = header.strip()
2125 if (header.lower().startswith('connection') or
2126 header.lower().startswith('proxy')):
2127 continue
2128 sock.send('%s\r\n' % header)
2129 sock.send('\r\n')
2130 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002131 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002132 self.send_response(500)
2133 self.end_headers()
2134 finally:
2135 if sock is not None:
2136 sock.close()
2137
2138 def do_CONNECT(self):
2139 try:
2140 pos = self.path.rfind(':')
2141 host = self.path[:pos]
2142 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002143 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002144 self.send_response(400)
2145 self.end_headers()
2146
2147 try:
2148 sock = socket.create_connection((host, port))
2149 self.send_response(200, 'Connection established')
2150 self.end_headers()
2151 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002152 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002153 self.send_response(500)
2154 self.end_headers()
2155 finally:
2156 sock.close()
2157
2158 def do_GET(self):
2159 self._do_common_method()
2160
2161 def do_HEAD(self):
2162 self._do_common_method()
2163
2164
mattm@chromium.org830a3712012-11-07 23:00:07 +00002165class ServerRunner(testserver_base.TestServerRunner):
2166 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002167
mattm@chromium.org830a3712012-11-07 23:00:07 +00002168 def __init__(self):
2169 super(ServerRunner, self).__init__()
2170 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002171
mattm@chromium.org830a3712012-11-07 23:00:07 +00002172 def __make_data_dir(self):
2173 if self.options.data_dir:
2174 if not os.path.isdir(self.options.data_dir):
2175 raise testserver_base.OptionError('specified data dir not found: ' +
2176 self.options.data_dir + ' exiting...')
2177 my_data_dir = self.options.data_dir
2178 else:
2179 # Create the default path to our data dir, relative to the exe dir.
2180 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2181 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002182
mattm@chromium.org830a3712012-11-07 23:00:07 +00002183 #TODO(ibrar): Must use Find* funtion defined in google\tools
2184 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002185
mattm@chromium.org830a3712012-11-07 23:00:07 +00002186 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002187
mattm@chromium.org830a3712012-11-07 23:00:07 +00002188 def create_server(self, server_data):
2189 port = self.options.port
2190 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002191
mattm@chromium.org830a3712012-11-07 23:00:07 +00002192 if self.options.server_type == SERVER_HTTP:
2193 if self.options.https:
2194 pem_cert_and_key = None
2195 if self.options.cert_and_key_file:
2196 if not os.path.isfile(self.options.cert_and_key_file):
2197 raise testserver_base.OptionError(
2198 'specified server cert file not found: ' +
2199 self.options.cert_and_key_file + ' exiting...')
2200 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002201 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002202 # generate a new certificate and run an OCSP server for it.
2203 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2204 print ('OCSP server started on %s:%d...' %
2205 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002206
mattm@chromium.org830a3712012-11-07 23:00:07 +00002207 ocsp_der = None
2208 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002209
mattm@chromium.org830a3712012-11-07 23:00:07 +00002210 if self.options.ocsp == 'ok':
2211 ocsp_state = minica.OCSP_STATE_GOOD
2212 elif self.options.ocsp == 'revoked':
2213 ocsp_state = minica.OCSP_STATE_REVOKED
2214 elif self.options.ocsp == 'invalid':
2215 ocsp_state = minica.OCSP_STATE_INVALID
2216 elif self.options.ocsp == 'unauthorized':
2217 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2218 elif self.options.ocsp == 'unknown':
2219 ocsp_state = minica.OCSP_STATE_UNKNOWN
2220 else:
2221 raise testserver_base.OptionError('unknown OCSP status: ' +
2222 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002223
mattm@chromium.org830a3712012-11-07 23:00:07 +00002224 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2225 subject = "127.0.0.1",
2226 ocsp_url = ("http://%s:%d/ocsp" %
2227 (host, self.__ocsp_server.server_port)),
2228 ocsp_state = ocsp_state)
2229
2230 self.__ocsp_server.ocsp_response = ocsp_der
2231
2232 for ca_cert in self.options.ssl_client_ca:
2233 if not os.path.isfile(ca_cert):
2234 raise testserver_base.OptionError(
2235 'specified trusted client CA file not found: ' + ca_cert +
2236 ' exiting...')
2237 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2238 self.options.ssl_client_auth,
2239 self.options.ssl_client_ca,
2240 self.options.ssl_bulk_cipher,
2241 self.options.record_resume,
2242 self.options.tls_intolerant)
2243 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2244 else:
2245 server = HTTPServer((host, port), TestPageHandler)
2246 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2247
2248 server.data_dir = self.__make_data_dir()
2249 server.file_root_url = self.options.file_root_url
2250 server_data['port'] = server.server_port
2251 server._device_management_handler = None
2252 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00002253 elif self.options.server_type == SERVER_WEBSOCKET:
2254 # Launch pywebsocket via WebSocketServer.
2255 logger = logging.getLogger()
2256 logger.addHandler(logging.StreamHandler())
2257 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2258 # is required to work correctly. It should be fixed from pywebsocket side.
2259 os.chdir(self.__make_data_dir())
2260 websocket_options = WebSocketOptions(host, port, '.')
2261 if self.options.cert_and_key_file:
2262 websocket_options.use_tls = True
2263 websocket_options.private_key = self.options.cert_and_key_file
2264 websocket_options.certificate = self.options.cert_and_key_file
2265 if self.options.ssl_client_auth:
2266 websocket_options.tls_client_auth = True
2267 if len(self.options.ssl_client_ca) != 1:
2268 raise testserver_base.OptionError(
2269 'one trusted client CA file should be specified')
2270 if not os.path.isfile(self.options.ssl_client_ca[0]):
2271 raise testserver_base.OptionError(
2272 'specified trusted client CA file not found: ' +
2273 self.options.ssl_client_ca[0] + ' exiting...')
2274 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2275 server = WebSocketServer(websocket_options)
2276 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2277 server_data['port'] = server.server_port
2278 elif self.options.server_type == SERVER_SYNC:
2279 xmpp_port = self.options.xmpp_port
2280 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2281 print 'Sync HTTP server started on port %d...' % server.server_port
2282 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2283 server_data['port'] = server.server_port
2284 server_data['xmpp_port'] = server.xmpp_port
2285 elif self.options.server_type == SERVER_TCP_ECHO:
2286 # Used for generating the key (randomly) that encodes the "echo request"
2287 # message.
2288 random.seed()
2289 server = TCPEchoServer((host, port), TCPEchoHandler)
2290 print 'Echo TCP server started on port %d...' % server.server_port
2291 server_data['port'] = server.server_port
2292 elif self.options.server_type == SERVER_UDP_ECHO:
2293 # Used for generating the key (randomly) that encodes the "echo request"
2294 # message.
2295 random.seed()
2296 server = UDPEchoServer((host, port), UDPEchoHandler)
2297 print 'Echo UDP server started on port %d...' % server.server_port
2298 server_data['port'] = server.server_port
2299 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2300 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2301 print 'BasicAuthProxy server started on port %d...' % server.server_port
2302 server_data['port'] = server.server_port
2303 elif self.options.server_type == SERVER_FTP:
2304 my_data_dir = self.__make_data_dir()
2305
2306 # Instantiate a dummy authorizer for managing 'virtual' users
2307 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2308
2309 # Define a new user having full r/w permissions and a read-only
2310 # anonymous user
2311 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2312
2313 authorizer.add_anonymous(my_data_dir)
2314
2315 # Instantiate FTP handler class
2316 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2317 ftp_handler.authorizer = authorizer
2318
2319 # Define a customized banner (string returned when client connects)
2320 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2321 pyftpdlib.ftpserver.__ver__)
2322
2323 # Instantiate FTP server class and listen to address:port
2324 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2325 server_data['port'] = server.socket.getsockname()[1]
2326 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002327 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002328 raise testserver_base.OptionError('unknown server type' +
2329 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002330
mattm@chromium.org830a3712012-11-07 23:00:07 +00002331 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002332
mattm@chromium.org830a3712012-11-07 23:00:07 +00002333 def run_server(self):
2334 if self.__ocsp_server:
2335 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002336
mattm@chromium.org830a3712012-11-07 23:00:07 +00002337 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002338
mattm@chromium.org830a3712012-11-07 23:00:07 +00002339 if self.__ocsp_server:
2340 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002341
mattm@chromium.org830a3712012-11-07 23:00:07 +00002342 def add_options(self):
2343 testserver_base.TestServerRunner.add_options(self)
2344 self.option_parser.add_option('-f', '--ftp', action='store_const',
2345 const=SERVER_FTP, default=SERVER_HTTP,
2346 dest='server_type',
2347 help='start up an FTP server.')
2348 self.option_parser.add_option('--sync', action='store_const',
2349 const=SERVER_SYNC, default=SERVER_HTTP,
2350 dest='server_type',
2351 help='start up a sync server.')
2352 self.option_parser.add_option('--tcp-echo', action='store_const',
2353 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2354 dest='server_type',
2355 help='start up a tcp echo server.')
2356 self.option_parser.add_option('--udp-echo', action='store_const',
2357 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2358 dest='server_type',
2359 help='start up a udp echo server.')
2360 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2361 const=SERVER_BASIC_AUTH_PROXY,
2362 default=SERVER_HTTP, dest='server_type',
2363 help='start up a proxy server which requires '
2364 'basic authentication.')
2365 self.option_parser.add_option('--websocket', action='store_const',
2366 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2367 dest='server_type',
2368 help='start up a WebSocket server.')
2369 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2370 help='Port used by the XMPP server. If '
2371 'unspecified, the XMPP server will listen on '
2372 'an ephemeral port.')
2373 self.option_parser.add_option('--data-dir', dest='data_dir',
2374 help='Directory from which to read the '
2375 'files.')
2376 self.option_parser.add_option('--https', action='store_true',
2377 dest='https', help='Specify that https '
2378 'should be used.')
2379 self.option_parser.add_option('--cert-and-key-file',
2380 dest='cert_and_key_file', help='specify the '
2381 'path to the file containing the certificate '
2382 'and private key for the server in PEM '
2383 'format')
2384 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2385 help='The type of OCSP response generated '
2386 'for the automatically generated '
2387 'certificate. One of [ok,revoked,invalid]')
2388 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2389 default='0', type='int',
2390 help='If nonzero, certain TLS connections '
2391 'will be aborted in order to test version '
2392 'fallback. 1 means all TLS versions will be '
2393 'aborted. 2 means TLS 1.1 or higher will be '
2394 'aborted. 3 means TLS 1.2 or higher will be '
2395 'aborted.')
2396 self.option_parser.add_option('--https-record-resume',
2397 dest='record_resume', const=True,
2398 default=False, action='store_const',
2399 help='Record resumption cache events rather '
2400 'than resuming as normal. Allows the use of '
2401 'the /ssl-session-cache request')
2402 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2403 help='Require SSL client auth on every '
2404 'connection.')
2405 self.option_parser.add_option('--ssl-client-ca', action='append',
2406 default=[], help='Specify that the client '
2407 'certificate request should include the CA '
2408 'named in the subject of the DER-encoded '
2409 'certificate contained in the specified '
2410 'file. This option may appear multiple '
2411 'times, indicating multiple CA names should '
2412 'be sent in the request.')
2413 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2414 help='Specify the bulk encryption '
2415 'algorithm(s) that will be accepted by the '
2416 'SSL server. Valid values are "aes256", '
2417 '"aes128", "3des", "rc4". If omitted, all '
2418 'algorithms will be used. This option may '
2419 'appear multiple times, indicating '
2420 'multiple algorithms should be enabled.');
2421 self.option_parser.add_option('--file-root-url', default='/files/',
2422 help='Specify a root URL for files served.')
2423 self.option_parser.add_option('--policy-key', action='append',
2424 dest='policy_keys',
2425 help='Specify a path to a PEM-encoded '
2426 'private key to use for policy signing. May '
2427 'be specified multiple times in order to '
2428 'load multipe keys into the server. If the '
2429 'server has multiple keys, it will rotate '
2430 'through them in at each request a '
2431 'round-robin fashion. The server will '
2432 'generate a random key if none is specified '
2433 'on the command line.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002434
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002435
initial.commit94958cf2008-07-26 22:42:52 +00002436if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002437 sys.exit(ServerRunner().main())