blob: 3679a0349cbc69119e097f85cb97aa59e277c0c7 [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
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000039import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000040import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000041import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000042import tlslite
43import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000044
mattm@chromium.org830a3712012-11-07 23:00:07 +000045BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000046sys.path.insert(
47 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
48from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000049
maruel@chromium.org756cf982009-03-05 12:46:38 +000050SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000051SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000052SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000053SERVER_TCP_ECHO = 3
54SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000055SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000056SERVER_WEBSOCKET = 6
57
58# Default request queue size for WebSocketServer.
59_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000060
mattm@chromium.org830a3712012-11-07 23:00:07 +000061
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000062# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000063debug_output = sys.stderr
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +000064def debug(string):
65 debug_output.write(string + "\n")
initial.commit94958cf2008-07-26 22:42:52 +000066 debug_output.flush()
67
mattm@chromium.org830a3712012-11-07 23:00:07 +000068
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000069class WebSocketOptions:
70 """Holds options for WebSocketServer."""
71
72 def __init__(self, host, port, data_dir):
73 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
74 self.server_host = host
75 self.port = port
76 self.websock_handlers = data_dir
77 self.scan_dir = None
78 self.allow_handlers_outside_root_dir = False
79 self.websock_handlers_map_file = None
80 self.cgi_directories = []
81 self.is_executable_method = None
82 self.allow_draft75 = False
83 self.strict = True
84
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000085 self.use_tls = False
86 self.private_key = None
87 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000088 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000089 self.tls_client_ca = None
90 self.use_basic_auth = False
91
mattm@chromium.org830a3712012-11-07 23:00:07 +000092
agl@chromium.orgf9e66792011-12-12 22:22:19 +000093class RecordingSSLSessionCache(object):
94 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
95 lookups and inserts in order to test session cache behaviours."""
96
97 def __init__(self):
98 self.log = []
99
100 def __getitem__(self, sessionID):
101 self.log.append(('lookup', sessionID))
102 raise KeyError()
103
104 def __setitem__(self, sessionID, session):
105 self.log.append(('insert', sessionID))
106
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000107
108class ClientRestrictingServerMixIn:
109 """Implements verify_request to limit connections to our configured IP
110 address."""
111
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000112 def verify_request(self, _request, client_address):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000113 return client_address[0] == self.server_address[0]
114
115
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000116class BrokenPipeHandlerMixIn:
117 """Allows the server to deal with "broken pipe" errors (which happen if the
118 browser quits with outstanding requests, like for the favicon). This mix-in
119 requires the class to derive from SocketServer.BaseServer and not override its
120 handle_error() method. """
121
122 def handle_error(self, request, client_address):
123 value = sys.exc_info()[1]
124 if isinstance(value, socket.error):
125 err = value.args[0]
126 if sys.platform in ('win32', 'cygwin'):
127 # "An established connection was aborted by the software in your host."
128 pipe_err = 10053
129 else:
130 pipe_err = errno.EPIPE
131 if err == pipe_err:
132 print "testserver.py: Broken pipe"
133 return
134 SocketServer.BaseServer.handle_error(self, request, client_address)
135
136
initial.commit94958cf2008-07-26 22:42:52 +0000137class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000138 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000139 to be exited cleanly (by setting its "stop" member to True)."""
140
141 def serve_forever(self):
142 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000143 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000144 while not self.stop:
145 self.handle_request()
146 self.socket.close()
147
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000149class HTTPServer(ClientRestrictingServerMixIn,
150 BrokenPipeHandlerMixIn,
151 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000152 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000153 verification."""
154
155 pass
156
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000157class OCSPServer(ClientRestrictingServerMixIn,
158 BrokenPipeHandlerMixIn,
159 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000160 """This is a specialization of HTTPServer that serves an
161 OCSP response"""
162
163 def serve_forever_on_thread(self):
164 self.thread = threading.Thread(target = self.serve_forever,
165 name = "OCSPServerThread")
166 self.thread.start()
167
168 def stop_serving(self):
169 self.shutdown()
170 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000171
mattm@chromium.org830a3712012-11-07 23:00:07 +0000172
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000173class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
174 ClientRestrictingServerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000175 BrokenPipeHandlerMixIn,
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000176 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000177 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000178 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000179
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000180 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000181 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000182 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000183 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
184 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000185 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000186 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000187 self.tls_intolerant = tls_intolerant
188
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000189 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000190 s = open(ca_file).read()
191 x509 = tlslite.api.X509()
192 x509.parse(s)
193 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000194 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
195 if ssl_bulk_ciphers is not None:
196 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000197
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000198 if record_resume_info:
199 # If record_resume_info is true then we'll replace the session cache with
200 # an object that records the lookups and inserts that it sees.
201 self.session_cache = RecordingSSLSessionCache()
202 else:
203 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000204 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
205
206 def handshake(self, tlsConnection):
207 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000208
initial.commit94958cf2008-07-26 22:42:52 +0000209 try:
210 tlsConnection.handshakeServer(certChain=self.cert_chain,
211 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000212 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000213 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000214 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000215 reqCAs=self.ssl_client_cas,
216 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000217 tlsConnection.ignoreAbruptClose = True
218 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000219 except tlslite.api.TLSAbruptCloseError:
220 # Ignore abrupt close.
221 return True
initial.commit94958cf2008-07-26 22:42:52 +0000222 except tlslite.api.TLSError, error:
223 print "Handshake failure:", str(error)
224 return False
225
akalin@chromium.org154bb132010-11-12 02:20:27 +0000226
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000227class SyncHTTPServer(ClientRestrictingServerMixIn,
228 BrokenPipeHandlerMixIn,
229 StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000230 """An HTTP server that handles sync commands."""
231
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000232 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000233 # We import here to avoid pulling in chromiumsync's dependencies
234 # unless strictly necessary.
235 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000236 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000237 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000238 self._sync_handler = chromiumsync.TestServer()
239 self._xmpp_socket_map = {}
240 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000241 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000242 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000243 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000244
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000245 def GetXmppServer(self):
246 return self._xmpp_server
247
akalin@chromium.org154bb132010-11-12 02:20:27 +0000248 def HandleCommand(self, query, raw_request):
249 return self._sync_handler.HandleCommand(query, raw_request)
250
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000251 def HandleRequestNoBlock(self):
252 """Handles a single request.
253
254 Copied from SocketServer._handle_request_noblock().
255 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000256
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000257 try:
258 request, client_address = self.get_request()
259 except socket.error:
260 return
261 if self.verify_request(request, client_address):
262 try:
263 self.process_request(request, client_address)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000264 except Exception:
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000265 self.handle_error(request, client_address)
266 self.close_request(request)
267
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000268 def SetAuthenticated(self, auth_valid):
269 self.authenticated = auth_valid
270
271 def GetAuthenticated(self):
272 return self.authenticated
273
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000274 def serve_forever(self):
275 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
276 """
277
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000278 def HandleXmppSocket(fd, socket_map, handler):
279 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000280
281 Adapted from asyncore.read() et al.
282 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000283
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000284 xmpp_connection = socket_map.get(fd)
285 # This could happen if a previous handler call caused fd to get
286 # removed from socket_map.
287 if xmpp_connection is None:
288 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000289 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000290 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000291 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
292 raise
293 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000294 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000295
296 while True:
297 read_fds = [ self.fileno() ]
298 write_fds = []
299 exceptional_fds = []
300
301 for fd, xmpp_connection in self._xmpp_socket_map.items():
302 is_r = xmpp_connection.readable()
303 is_w = xmpp_connection.writable()
304 if is_r:
305 read_fds.append(fd)
306 if is_w:
307 write_fds.append(fd)
308 if is_r or is_w:
309 exceptional_fds.append(fd)
310
311 try:
312 read_fds, write_fds, exceptional_fds = (
313 select.select(read_fds, write_fds, exceptional_fds))
314 except select.error, err:
315 if err.args[0] != errno.EINTR:
316 raise
317 else:
318 continue
319
320 for fd in read_fds:
321 if fd == self.fileno():
322 self.HandleRequestNoBlock()
323 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000324 HandleXmppSocket(fd, self._xmpp_socket_map,
325 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000326
327 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000328 HandleXmppSocket(fd, self._xmpp_socket_map,
329 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000330
331 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000332 HandleXmppSocket(fd, self._xmpp_socket_map,
333 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000334
akalin@chromium.org154bb132010-11-12 02:20:27 +0000335
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000336class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
337 """This is a specialization of FTPServer that adds client verification."""
338
339 pass
340
341
342class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000343 """A TCP echo server that echoes back what it has received."""
344
345 def server_bind(self):
346 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000347
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000348 SocketServer.TCPServer.server_bind(self)
349 host, port = self.socket.getsockname()[:2]
350 self.server_name = socket.getfqdn(host)
351 self.server_port = port
352
353 def serve_forever(self):
354 self.stop = False
355 self.nonce_time = None
356 while not self.stop:
357 self.handle_request()
358 self.socket.close()
359
360
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000361class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000362 """A UDP echo server that echoes back what it has received."""
363
364 def server_bind(self):
365 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000366
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000367 SocketServer.UDPServer.server_bind(self)
368 host, port = self.socket.getsockname()[:2]
369 self.server_name = socket.getfqdn(host)
370 self.server_port = port
371
372 def serve_forever(self):
373 self.stop = False
374 self.nonce_time = None
375 while not self.stop:
376 self.handle_request()
377 self.socket.close()
378
379
akalin@chromium.org154bb132010-11-12 02:20:27 +0000380class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
381
382 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000383 connect_handlers, get_handlers, head_handlers, post_handlers,
384 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000385 self._connect_handlers = connect_handlers
386 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000387 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000388 self._post_handlers = post_handlers
389 self._put_handlers = put_handlers
390 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
391 self, request, client_address, socket_server)
392
393 def log_request(self, *args, **kwargs):
394 # Disable request logging to declutter test log output.
395 pass
396
397 def _ShouldHandleRequest(self, handler_name):
398 """Determines if the path can be handled by the handler.
399
400 We consider a handler valid if the path begins with the
401 handler name. It can optionally be followed by "?*", "/*".
402 """
403
404 pattern = re.compile('%s($|\?|/).*' % handler_name)
405 return pattern.match(self.path)
406
407 def do_CONNECT(self):
408 for handler in self._connect_handlers:
409 if handler():
410 return
411
412 def do_GET(self):
413 for handler in self._get_handlers:
414 if handler():
415 return
416
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000417 def do_HEAD(self):
418 for handler in self._head_handlers:
419 if handler():
420 return
421
akalin@chromium.org154bb132010-11-12 02:20:27 +0000422 def do_POST(self):
423 for handler in self._post_handlers:
424 if handler():
425 return
426
427 def do_PUT(self):
428 for handler in self._put_handlers:
429 if handler():
430 return
431
432
433class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000434
435 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000436 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000437 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000438 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000439 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000440 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.NoCacheMaxAgeTimeHandler,
442 self.NoCacheTimeHandler,
443 self.CacheTimeHandler,
444 self.CacheExpiresHandler,
445 self.CacheProxyRevalidateHandler,
446 self.CachePrivateHandler,
447 self.CachePublicHandler,
448 self.CacheSMaxAgeHandler,
449 self.CacheMustRevalidateHandler,
450 self.CacheMustRevalidateMaxAgeHandler,
451 self.CacheNoStoreHandler,
452 self.CacheNoStoreMaxAgeHandler,
453 self.CacheNoTransformHandler,
454 self.DownloadHandler,
455 self.DownloadFinishHandler,
456 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000457 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000458 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000459 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000460 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000461 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000462 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000463 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000464 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000465 self.AuthBasicHandler,
466 self.AuthDigestHandler,
467 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000468 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000469 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000470 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000471 self.ServerRedirectHandler,
472 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000473 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000474 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000475 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000476 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000477 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000478 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000479 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000480 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000481 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000482 self.DeviceManagementHandler,
483 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000484 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000485 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000486 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000487 head_handlers = [
488 self.FileHandler,
489 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000490
maruel@google.come250a9b2009-03-10 17:39:46 +0000491 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000492 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000493 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 'gif': 'image/gif',
495 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000496 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000497 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000498 'pdf' : 'application/pdf',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000499 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000500 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000501 }
initial.commit94958cf2008-07-26 22:42:52 +0000502 self._default_mime_type = 'text/html'
503
akalin@chromium.org154bb132010-11-12 02:20:27 +0000504 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000505 connect_handlers, get_handlers, head_handlers,
506 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507
initial.commit94958cf2008-07-26 22:42:52 +0000508 def GetMIMETypeFromName(self, file_name):
509 """Returns the mime type for the specified file_name. So far it only looks
510 at the file extension."""
511
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000512 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000513 if len(extension) == 0:
514 # no extension.
515 return self._default_mime_type
516
ericroman@google.comc17ca532009-05-07 03:51:05 +0000517 # extension starts with a dot, so we need to remove it
518 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000519
initial.commit94958cf2008-07-26 22:42:52 +0000520 def NoCacheMaxAgeTimeHandler(self):
521 """This request handler yields a page with the title set to the current
522 system time, and no caching requested."""
523
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000524 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000525 return False
526
527 self.send_response(200)
528 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000529 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000530 self.end_headers()
531
maruel@google.come250a9b2009-03-10 17:39:46 +0000532 self.wfile.write('<html><head><title>%s</title></head></html>' %
533 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000534
535 return True
536
537 def NoCacheTimeHandler(self):
538 """This request handler yields a page with the title set to the current
539 system time, and no caching requested."""
540
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000541 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000542 return False
543
544 self.send_response(200)
545 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000546 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000547 self.end_headers()
548
maruel@google.come250a9b2009-03-10 17:39:46 +0000549 self.wfile.write('<html><head><title>%s</title></head></html>' %
550 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000551
552 return True
553
554 def CacheTimeHandler(self):
555 """This request handler yields a page with the title set to the current
556 system time, and allows caching for one minute."""
557
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000558 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000559 return False
560
561 self.send_response(200)
562 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000563 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000564 self.end_headers()
565
maruel@google.come250a9b2009-03-10 17:39:46 +0000566 self.wfile.write('<html><head><title>%s</title></head></html>' %
567 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000568
569 return True
570
571 def CacheExpiresHandler(self):
572 """This request handler yields a page with the title set to the current
573 system time, and set the page to expire on 1 Jan 2099."""
574
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000575 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000576 return False
577
578 self.send_response(200)
579 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000580 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000581 self.end_headers()
582
maruel@google.come250a9b2009-03-10 17:39:46 +0000583 self.wfile.write('<html><head><title>%s</title></head></html>' %
584 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000585
586 return True
587
588 def CacheProxyRevalidateHandler(self):
589 """This request handler yields a page with the title set to the current
590 system time, and allows caching for 60 seconds"""
591
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000592 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000593 return False
594
595 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000596 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000597 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
598 self.end_headers()
599
maruel@google.come250a9b2009-03-10 17:39:46 +0000600 self.wfile.write('<html><head><title>%s</title></head></html>' %
601 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000602
603 return True
604
605 def CachePrivateHandler(self):
606 """This request handler yields a page with the title set to the current
607 system time, and allows caching for 5 seconds."""
608
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000609 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000610 return False
611
612 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000613 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000614 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000615 self.end_headers()
616
maruel@google.come250a9b2009-03-10 17:39:46 +0000617 self.wfile.write('<html><head><title>%s</title></head></html>' %
618 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000619
620 return True
621
622 def CachePublicHandler(self):
623 """This request handler yields a page with the title set to the current
624 system time, and allows caching for 5 seconds."""
625
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000626 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000627 return False
628
629 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000630 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000631 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000632 self.end_headers()
633
maruel@google.come250a9b2009-03-10 17:39:46 +0000634 self.wfile.write('<html><head><title>%s</title></head></html>' %
635 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000636
637 return True
638
639 def CacheSMaxAgeHandler(self):
640 """This request handler yields a page with the title set to the current
641 system time, and does not allow for caching."""
642
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000643 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000644 return False
645
646 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000647 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000648 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
649 self.end_headers()
650
maruel@google.come250a9b2009-03-10 17:39:46 +0000651 self.wfile.write('<html><head><title>%s</title></head></html>' %
652 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000653
654 return True
655
656 def CacheMustRevalidateHandler(self):
657 """This request handler yields a page with the title set to the current
658 system time, and does not allow caching."""
659
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000660 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000661 return False
662
663 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000664 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000665 self.send_header('Cache-Control', 'must-revalidate')
666 self.end_headers()
667
maruel@google.come250a9b2009-03-10 17:39:46 +0000668 self.wfile.write('<html><head><title>%s</title></head></html>' %
669 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000670
671 return True
672
673 def CacheMustRevalidateMaxAgeHandler(self):
674 """This request handler yields a page with the title set to the current
675 system time, and does not allow caching event though max-age of 60
676 seconds is specified."""
677
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000678 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000679 return False
680
681 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000682 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000683 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
684 self.end_headers()
685
maruel@google.come250a9b2009-03-10 17:39:46 +0000686 self.wfile.write('<html><head><title>%s</title></head></html>' %
687 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000688
689 return True
690
initial.commit94958cf2008-07-26 22:42:52 +0000691 def CacheNoStoreHandler(self):
692 """This request handler yields a page with the title set to the current
693 system time, and does not allow the page to be stored."""
694
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000695 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000696 return False
697
698 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000699 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000700 self.send_header('Cache-Control', 'no-store')
701 self.end_headers()
702
maruel@google.come250a9b2009-03-10 17:39:46 +0000703 self.wfile.write('<html><head><title>%s</title></head></html>' %
704 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000705
706 return True
707
708 def CacheNoStoreMaxAgeHandler(self):
709 """This request handler yields a page with the title set to the current
710 system time, and does not allow the page to be stored even though max-age
711 of 60 seconds is specified."""
712
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000713 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000714 return False
715
716 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000717 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000718 self.send_header('Cache-Control', 'max-age=60, no-store')
719 self.end_headers()
720
maruel@google.come250a9b2009-03-10 17:39:46 +0000721 self.wfile.write('<html><head><title>%s</title></head></html>' %
722 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000723
724 return True
725
726
727 def CacheNoTransformHandler(self):
728 """This request handler yields a page with the title set to the current
729 system time, and does not allow the content to transformed during
730 user-agent caching"""
731
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000732 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000733 return False
734
735 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000736 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000737 self.send_header('Cache-Control', 'no-transform')
738 self.end_headers()
739
maruel@google.come250a9b2009-03-10 17:39:46 +0000740 self.wfile.write('<html><head><title>%s</title></head></html>' %
741 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000742
743 return True
744
745 def EchoHeader(self):
746 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000747
ananta@chromium.org219b2062009-10-23 16:09:41 +0000748 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000749
ananta@chromium.org56812d02011-04-07 17:52:05 +0000750 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000751 """This function echoes back the value of a specific request header while
752 allowing caching for 16 hours."""
753
ananta@chromium.org56812d02011-04-07 17:52:05 +0000754 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000755
756 def EchoHeaderHelper(self, echo_header):
757 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000758
ananta@chromium.org219b2062009-10-23 16:09:41 +0000759 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000760 return False
761
762 query_char = self.path.find('?')
763 if query_char != -1:
764 header_name = self.path[query_char+1:]
765
766 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000767 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000768 if echo_header == '/echoheadercache':
769 self.send_header('Cache-control', 'max-age=60000')
770 else:
771 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000772 # insert a vary header to properly indicate that the cachability of this
773 # request is subject to value of the request header being echoed.
774 if len(header_name) > 0:
775 self.send_header('Vary', header_name)
776 self.end_headers()
777
778 if len(header_name) > 0:
779 self.wfile.write(self.headers.getheader(header_name))
780
781 return True
782
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000783 def ReadRequestBody(self):
784 """This function reads the body of the current HTTP request, handling
785 both plain and chunked transfer encoded requests."""
786
787 if self.headers.getheader('transfer-encoding') != 'chunked':
788 length = int(self.headers.getheader('content-length'))
789 return self.rfile.read(length)
790
791 # Read the request body as chunks.
792 body = ""
793 while True:
794 line = self.rfile.readline()
795 length = int(line, 16)
796 if length == 0:
797 self.rfile.readline()
798 break
799 body += self.rfile.read(length)
800 self.rfile.read(2)
801 return body
802
initial.commit94958cf2008-07-26 22:42:52 +0000803 def EchoHandler(self):
804 """This handler just echoes back the payload of the request, for testing
805 form submission."""
806
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000807 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000808 return False
809
810 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000811 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000812 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000813 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000814 return True
815
816 def EchoTitleHandler(self):
817 """This handler is like Echo, but sets the page title to the request."""
818
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000819 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000820 return False
821
822 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000823 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000824 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000825 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000826 self.wfile.write('<html><head><title>')
827 self.wfile.write(request)
828 self.wfile.write('</title></head></html>')
829 return True
830
831 def EchoAllHandler(self):
832 """This handler yields a (more) human-readable page listing information
833 about the request header & contents."""
834
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000835 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000836 return False
837
838 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000839 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000840 self.end_headers()
841 self.wfile.write('<html><head><style>'
842 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
843 '</style></head><body>'
844 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000845 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000846 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000847
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000848 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000849 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000850 params = cgi.parse_qs(qs, keep_blank_values=1)
851
852 for param in params:
853 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000854
855 self.wfile.write('</pre>')
856
857 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
858
859 self.wfile.write('</body></html>')
860 return True
861
862 def DownloadHandler(self):
863 """This handler sends a downloadable file with or without reporting
864 the size (6K)."""
865
866 if self.path.startswith("/download-unknown-size"):
867 send_length = False
868 elif self.path.startswith("/download-known-size"):
869 send_length = True
870 else:
871 return False
872
873 #
874 # The test which uses this functionality is attempting to send
875 # small chunks of data to the client. Use a fairly large buffer
876 # so that we'll fill chrome's IO buffer enough to force it to
877 # actually write the data.
878 # See also the comments in the client-side of this test in
879 # download_uitest.cc
880 #
881 size_chunk1 = 35*1024
882 size_chunk2 = 10*1024
883
884 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000885 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000886 self.send_header('Cache-Control', 'max-age=0')
887 if send_length:
888 self.send_header('Content-Length', size_chunk1 + size_chunk2)
889 self.end_headers()
890
891 # First chunk of data:
892 self.wfile.write("*" * size_chunk1)
893 self.wfile.flush()
894
895 # handle requests until one of them clears this flag.
896 self.server.waitForDownload = True
897 while self.server.waitForDownload:
898 self.server.handle_request()
899
900 # Second chunk of data:
901 self.wfile.write("*" * size_chunk2)
902 return True
903
904 def DownloadFinishHandler(self):
905 """This handler just tells the server to finish the current download."""
906
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000907 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000908 return False
909
910 self.server.waitForDownload = False
911 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000912 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000913 self.send_header('Cache-Control', 'max-age=0')
914 self.end_headers()
915 return True
916
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000917 def _ReplaceFileData(self, data, query_parameters):
918 """Replaces matching substrings in a file.
919
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000920 If the 'replace_text' URL query parameter is present, it is expected to be
921 of the form old_text:new_text, which indicates that any old_text strings in
922 the file are replaced with new_text. Multiple 'replace_text' parameters may
923 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000924
925 If the parameters are not present, |data| is returned.
926 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000927
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000928 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000929 replace_text_values = query_dict.get('replace_text', [])
930 for replace_text_value in replace_text_values:
931 replace_text_args = replace_text_value.split(':')
932 if len(replace_text_args) != 2:
933 raise ValueError(
934 'replace_text must be of form old_text:new_text. Actual value: %s' %
935 replace_text_value)
936 old_text_b64, new_text_b64 = replace_text_args
937 old_text = base64.urlsafe_b64decode(old_text_b64)
938 new_text = base64.urlsafe_b64decode(new_text_b64)
939 data = data.replace(old_text, new_text)
940 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000941
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000942 def ZipFileHandler(self):
943 """This handler sends the contents of the requested file in compressed form.
944 Can pass in a parameter that specifies that the content length be
945 C - the compressed size (OK),
946 U - the uncompressed size (Non-standard, but handled),
947 S - less than compressed (OK because we keep going),
948 M - larger than compressed but less than uncompressed (an error),
949 L - larger than uncompressed (an error)
950 Example: compressedfiles/Picture_1.doc?C
951 """
952
953 prefix = "/compressedfiles/"
954 if not self.path.startswith(prefix):
955 return False
956
957 # Consume a request body if present.
958 if self.command == 'POST' or self.command == 'PUT' :
959 self.ReadRequestBody()
960
961 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
962
963 if not query in ('C', 'U', 'S', 'M', 'L'):
964 return False
965
966 sub_path = url_path[len(prefix):]
967 entries = sub_path.split('/')
968 file_path = os.path.join(self.server.data_dir, *entries)
969 if os.path.isdir(file_path):
970 file_path = os.path.join(file_path, 'index.html')
971
972 if not os.path.isfile(file_path):
973 print "File not found " + sub_path + " full path:" + file_path
974 self.send_error(404)
975 return True
976
977 f = open(file_path, "rb")
978 data = f.read()
979 uncompressed_len = len(data)
980 f.close()
981
982 # Compress the data.
983 data = zlib.compress(data)
984 compressed_len = len(data)
985
986 content_length = compressed_len
987 if query == 'U':
988 content_length = uncompressed_len
989 elif query == 'S':
990 content_length = compressed_len / 2
991 elif query == 'M':
992 content_length = (compressed_len + uncompressed_len) / 2
993 elif query == 'L':
994 content_length = compressed_len + uncompressed_len
995
996 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000997 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000998 self.send_header('Content-encoding', 'deflate')
999 self.send_header('Connection', 'close')
1000 self.send_header('Content-Length', content_length)
1001 self.send_header('ETag', '\'' + file_path + '\'')
1002 self.end_headers()
1003
1004 self.wfile.write(data)
1005
1006 return True
1007
initial.commit94958cf2008-07-26 22:42:52 +00001008 def FileHandler(self):
1009 """This handler sends the contents of the requested file. Wow, it's like
1010 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001011
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001012 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +00001013 if not self.path.startswith(prefix):
1014 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001015 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +00001016
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001017 def PostOnlyFileHandler(self):
1018 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001019
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +00001020 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001021 if not self.path.startswith(prefix):
1022 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001023 return self._FileHandlerHelper(prefix)
1024
1025 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001026 request_body = ''
1027 if self.command == 'POST' or self.command == 'PUT':
1028 # Consume a request body if present.
1029 request_body = self.ReadRequestBody()
1030
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001031 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001032 query_dict = cgi.parse_qs(query)
1033
1034 expected_body = query_dict.get('expected_body', [])
1035 if expected_body and request_body not in expected_body:
1036 self.send_response(404)
1037 self.end_headers()
1038 self.wfile.write('')
1039 return True
1040
1041 expected_headers = query_dict.get('expected_headers', [])
1042 for expected_header in expected_headers:
1043 header_name, expected_value = expected_header.split(':')
1044 if self.headers.getheader(header_name) != expected_value:
1045 self.send_response(404)
1046 self.end_headers()
1047 self.wfile.write('')
1048 return True
1049
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001050 sub_path = url_path[len(prefix):]
1051 entries = sub_path.split('/')
1052 file_path = os.path.join(self.server.data_dir, *entries)
1053 if os.path.isdir(file_path):
1054 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001055
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001056 if not os.path.isfile(file_path):
1057 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001058 self.send_error(404)
1059 return True
1060
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001061 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001062 data = f.read()
1063 f.close()
1064
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001065 data = self._ReplaceFileData(data, query)
1066
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001067 old_protocol_version = self.protocol_version
1068
initial.commit94958cf2008-07-26 22:42:52 +00001069 # If file.mock-http-headers exists, it contains the headers we
1070 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001071 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001072 if os.path.isfile(headers_path):
1073 f = open(headers_path, "r")
1074
1075 # "HTTP/1.1 200 OK"
1076 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001077 http_major, http_minor, status_code = re.findall(
1078 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1079 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001080 self.send_response(int(status_code))
1081
1082 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001083 header_values = re.findall('(\S+):\s*(.*)', line)
1084 if len(header_values) > 0:
1085 # "name: value"
1086 name, value = header_values[0]
1087 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001088 f.close()
1089 else:
1090 # Could be more generic once we support mime-type sniffing, but for
1091 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001092
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001093 range_header = self.headers.get('Range')
1094 if range_header and range_header.startswith('bytes='):
1095 # Note this doesn't handle all valid byte range_header values (i.e.
1096 # left open ended ones), just enough for what we needed so far.
1097 range_header = range_header[6:].split('-')
1098 start = int(range_header[0])
1099 if range_header[1]:
1100 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001101 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001102 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001103
1104 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001105 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1106 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001107 self.send_header('Content-Range', content_range)
1108 data = data[start: end + 1]
1109 else:
1110 self.send_response(200)
1111
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001112 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001113 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001114 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001115 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001116 self.end_headers()
1117
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001118 if (self.command != 'HEAD'):
1119 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001120
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001121 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001122 return True
1123
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001124 def SetCookieHandler(self):
1125 """This handler just sets a cookie, for testing cookie handling."""
1126
1127 if not self._ShouldHandleRequest("/set-cookie"):
1128 return False
1129
1130 query_char = self.path.find('?')
1131 if query_char != -1:
1132 cookie_values = self.path[query_char + 1:].split('&')
1133 else:
1134 cookie_values = ("",)
1135 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001136 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001137 for cookie_value in cookie_values:
1138 self.send_header('Set-Cookie', '%s' % cookie_value)
1139 self.end_headers()
1140 for cookie_value in cookie_values:
1141 self.wfile.write('%s' % cookie_value)
1142 return True
1143
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001144 def SetManyCookiesHandler(self):
1145 """This handler just sets a given number of cookies, for testing handling
1146 of large numbers of cookies."""
1147
1148 if not self._ShouldHandleRequest("/set-many-cookies"):
1149 return False
1150
1151 query_char = self.path.find('?')
1152 if query_char != -1:
1153 num_cookies = int(self.path[query_char + 1:])
1154 else:
1155 num_cookies = 0
1156 self.send_response(200)
1157 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001158 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001159 self.send_header('Set-Cookie', 'a=')
1160 self.end_headers()
1161 self.wfile.write('%d cookies were sent' % num_cookies)
1162 return True
1163
mattm@chromium.org983fc462012-06-30 00:52:08 +00001164 def ExpectAndSetCookieHandler(self):
1165 """Expects some cookies to be sent, and if they are, sets more cookies.
1166
1167 The expect parameter specifies a required cookie. May be specified multiple
1168 times.
1169 The set parameter specifies a cookie to set if all required cookies are
1170 preset. May be specified multiple times.
1171 The data parameter specifies the response body data to be returned."""
1172
1173 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1174 return False
1175
1176 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1177 query_dict = cgi.parse_qs(query)
1178 cookies = set()
1179 if 'Cookie' in self.headers:
1180 cookie_header = self.headers.getheader('Cookie')
1181 cookies.update([s.strip() for s in cookie_header.split(';')])
1182 got_all_expected_cookies = True
1183 for expected_cookie in query_dict.get('expect', []):
1184 if expected_cookie not in cookies:
1185 got_all_expected_cookies = False
1186 self.send_response(200)
1187 self.send_header('Content-Type', 'text/html')
1188 if got_all_expected_cookies:
1189 for cookie_value in query_dict.get('set', []):
1190 self.send_header('Set-Cookie', '%s' % cookie_value)
1191 self.end_headers()
1192 for data_value in query_dict.get('data', []):
1193 self.wfile.write(data_value)
1194 return True
1195
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001196 def SetHeaderHandler(self):
1197 """This handler sets a response header. Parameters are in the
1198 key%3A%20value&key2%3A%20value2 format."""
1199
1200 if not self._ShouldHandleRequest("/set-header"):
1201 return False
1202
1203 query_char = self.path.find('?')
1204 if query_char != -1:
1205 headers_values = self.path[query_char + 1:].split('&')
1206 else:
1207 headers_values = ("",)
1208 self.send_response(200)
1209 self.send_header('Content-Type', 'text/html')
1210 for header_value in headers_values:
1211 header_value = urllib.unquote(header_value)
1212 (key, value) = header_value.split(': ', 1)
1213 self.send_header(key, value)
1214 self.end_headers()
1215 for header_value in headers_values:
1216 self.wfile.write('%s' % header_value)
1217 return True
1218
initial.commit94958cf2008-07-26 22:42:52 +00001219 def AuthBasicHandler(self):
1220 """This handler tests 'Basic' authentication. It just sends a page with
1221 title 'user/pass' if you succeed."""
1222
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001223 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001224 return False
1225
1226 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001227 expected_password = 'secret'
1228 realm = 'testrealm'
1229 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001230
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001231 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1232 query_params = cgi.parse_qs(query, True)
1233 if 'set-cookie-if-challenged' in query_params:
1234 set_cookie_if_challenged = True
1235 if 'password' in query_params:
1236 expected_password = query_params['password'][0]
1237 if 'realm' in query_params:
1238 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001239
initial.commit94958cf2008-07-26 22:42:52 +00001240 auth = self.headers.getheader('authorization')
1241 try:
1242 if not auth:
1243 raise Exception('no auth')
1244 b64str = re.findall(r'Basic (\S+)', auth)[0]
1245 userpass = base64.b64decode(b64str)
1246 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001247 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001248 raise Exception('wrong password')
1249 except Exception, e:
1250 # Authentication failed.
1251 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001252 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001253 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001254 if set_cookie_if_challenged:
1255 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001256 self.end_headers()
1257 self.wfile.write('<html><head>')
1258 self.wfile.write('<title>Denied: %s</title>' % e)
1259 self.wfile.write('</head><body>')
1260 self.wfile.write('auth=%s<p>' % auth)
1261 self.wfile.write('b64str=%s<p>' % b64str)
1262 self.wfile.write('username: %s<p>' % username)
1263 self.wfile.write('userpass: %s<p>' % userpass)
1264 self.wfile.write('password: %s<p>' % password)
1265 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1266 self.wfile.write('</body></html>')
1267 return True
1268
1269 # Authentication successful. (Return a cachable response to allow for
1270 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001271 old_protocol_version = self.protocol_version
1272 self.protocol_version = "HTTP/1.1"
1273
initial.commit94958cf2008-07-26 22:42:52 +00001274 if_none_match = self.headers.getheader('if-none-match')
1275 if if_none_match == "abc":
1276 self.send_response(304)
1277 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001278 elif url_path.endswith(".gif"):
1279 # Using chrome/test/data/google/logo.gif as the test image
1280 test_image_path = ['google', 'logo.gif']
1281 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1282 if not os.path.isfile(gif_path):
1283 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001284 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001285 return True
1286
1287 f = open(gif_path, "rb")
1288 data = f.read()
1289 f.close()
1290
1291 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001292 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001293 self.send_header('Cache-control', 'max-age=60000')
1294 self.send_header('Etag', 'abc')
1295 self.end_headers()
1296 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001297 else:
1298 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001299 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001300 self.send_header('Cache-control', 'max-age=60000')
1301 self.send_header('Etag', 'abc')
1302 self.end_headers()
1303 self.wfile.write('<html><head>')
1304 self.wfile.write('<title>%s/%s</title>' % (username, password))
1305 self.wfile.write('</head><body>')
1306 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001307 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001308 self.wfile.write('</body></html>')
1309
rvargas@google.com54453b72011-05-19 01:11:11 +00001310 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001311 return True
1312
tonyg@chromium.org75054202010-03-31 22:06:10 +00001313 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001314 """Returns a nonce that's stable per request path for the server's lifetime.
1315 This is a fake implementation. A real implementation would only use a given
1316 nonce a single time (hence the name n-once). However, for the purposes of
1317 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001318
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001319 Args:
1320 force_reset: Iff set, the nonce will be changed. Useful for testing the
1321 "stale" response.
1322 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001323
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001324 if force_reset or not self.server.nonce_time:
1325 self.server.nonce_time = time.time()
1326 return hashlib.md5('privatekey%s%d' %
1327 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001328
1329 def AuthDigestHandler(self):
1330 """This handler tests 'Digest' authentication.
1331
1332 It just sends a page with title 'user/pass' if you succeed.
1333
1334 A stale response is sent iff "stale" is present in the request path.
1335 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001336
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001337 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001338 return False
1339
tonyg@chromium.org75054202010-03-31 22:06:10 +00001340 stale = 'stale' in self.path
1341 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001342 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001343 password = 'secret'
1344 realm = 'testrealm'
1345
1346 auth = self.headers.getheader('authorization')
1347 pairs = {}
1348 try:
1349 if not auth:
1350 raise Exception('no auth')
1351 if not auth.startswith('Digest'):
1352 raise Exception('not digest')
1353 # Pull out all the name="value" pairs as a dictionary.
1354 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1355
1356 # Make sure it's all valid.
1357 if pairs['nonce'] != nonce:
1358 raise Exception('wrong nonce')
1359 if pairs['opaque'] != opaque:
1360 raise Exception('wrong opaque')
1361
1362 # Check the 'response' value and make sure it matches our magic hash.
1363 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001364 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001365 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001366 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001367 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001368 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001369 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1370 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001371 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001372
1373 if pairs['response'] != response:
1374 raise Exception('wrong password')
1375 except Exception, e:
1376 # Authentication failed.
1377 self.send_response(401)
1378 hdr = ('Digest '
1379 'realm="%s", '
1380 'domain="/", '
1381 'qop="auth", '
1382 'algorithm=MD5, '
1383 'nonce="%s", '
1384 'opaque="%s"') % (realm, nonce, opaque)
1385 if stale:
1386 hdr += ', stale="TRUE"'
1387 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001388 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001389 self.end_headers()
1390 self.wfile.write('<html><head>')
1391 self.wfile.write('<title>Denied: %s</title>' % e)
1392 self.wfile.write('</head><body>')
1393 self.wfile.write('auth=%s<p>' % auth)
1394 self.wfile.write('pairs=%s<p>' % pairs)
1395 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1396 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1397 self.wfile.write('</body></html>')
1398 return True
1399
1400 # Authentication successful.
1401 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001402 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001403 self.end_headers()
1404 self.wfile.write('<html><head>')
1405 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1406 self.wfile.write('</head><body>')
1407 self.wfile.write('auth=%s<p>' % auth)
1408 self.wfile.write('pairs=%s<p>' % pairs)
1409 self.wfile.write('</body></html>')
1410
1411 return True
1412
1413 def SlowServerHandler(self):
1414 """Wait for the user suggested time before responding. The syntax is
1415 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001416
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001417 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001418 return False
1419 query_char = self.path.find('?')
1420 wait_sec = 1.0
1421 if query_char >= 0:
1422 try:
1423 wait_sec = int(self.path[query_char + 1:])
1424 except ValueError:
1425 pass
1426 time.sleep(wait_sec)
1427 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001428 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001429 self.end_headers()
1430 self.wfile.write("waited %d seconds" % wait_sec)
1431 return True
1432
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001433 def ChunkedServerHandler(self):
1434 """Send chunked response. Allows to specify chunks parameters:
1435 - waitBeforeHeaders - ms to wait before sending headers
1436 - waitBetweenChunks - ms to wait between chunks
1437 - chunkSize - size of each chunk in bytes
1438 - chunksNumber - number of chunks
1439 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1440 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001441
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001442 if not self._ShouldHandleRequest("/chunked"):
1443 return False
1444 query_char = self.path.find('?')
1445 chunkedSettings = {'waitBeforeHeaders' : 0,
1446 'waitBetweenChunks' : 0,
1447 'chunkSize' : 5,
1448 'chunksNumber' : 5}
1449 if query_char >= 0:
1450 params = self.path[query_char + 1:].split('&')
1451 for param in params:
1452 keyValue = param.split('=')
1453 if len(keyValue) == 2:
1454 try:
1455 chunkedSettings[keyValue[0]] = int(keyValue[1])
1456 except ValueError:
1457 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001458 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001459 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1460 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001461 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001462 self.send_header('Connection', 'close')
1463 self.send_header('Transfer-Encoding', 'chunked')
1464 self.end_headers()
1465 # Chunked encoding: sending all chunks, then final zero-length chunk and
1466 # then final CRLF.
1467 for i in range(0, chunkedSettings['chunksNumber']):
1468 if i > 0:
1469 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1470 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001471 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001472 self.sendChunkHelp('')
1473 return True
1474
initial.commit94958cf2008-07-26 22:42:52 +00001475 def ContentTypeHandler(self):
1476 """Returns a string of html with the given content type. E.g.,
1477 /contenttype?text/css returns an html file with the Content-Type
1478 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001479
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001480 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001481 return False
1482 query_char = self.path.find('?')
1483 content_type = self.path[query_char + 1:].strip()
1484 if not content_type:
1485 content_type = 'text/html'
1486 self.send_response(200)
1487 self.send_header('Content-Type', content_type)
1488 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001489 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001490 return True
1491
creis@google.com2f4f6a42011-03-25 19:44:19 +00001492 def NoContentHandler(self):
1493 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001494
creis@google.com2f4f6a42011-03-25 19:44:19 +00001495 if not self._ShouldHandleRequest("/nocontent"):
1496 return False
1497 self.send_response(204)
1498 self.end_headers()
1499 return True
1500
initial.commit94958cf2008-07-26 22:42:52 +00001501 def ServerRedirectHandler(self):
1502 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001503 '/server-redirect?http://foo.bar/asdf' to redirect to
1504 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001505
1506 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001507 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001508 return False
1509
1510 query_char = self.path.find('?')
1511 if query_char < 0 or len(self.path) <= query_char + 1:
1512 self.sendRedirectHelp(test_name)
1513 return True
1514 dest = self.path[query_char + 1:]
1515
1516 self.send_response(301) # moved permanently
1517 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001518 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001519 self.end_headers()
1520 self.wfile.write('<html><head>')
1521 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1522
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001523 return True
initial.commit94958cf2008-07-26 22:42:52 +00001524
1525 def ClientRedirectHandler(self):
1526 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001527 '/client-redirect?http://foo.bar/asdf' to redirect to
1528 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001529
1530 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001531 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001532 return False
1533
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001534 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001535 if query_char < 0 or len(self.path) <= query_char + 1:
1536 self.sendRedirectHelp(test_name)
1537 return True
1538 dest = self.path[query_char + 1:]
1539
1540 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001541 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001542 self.end_headers()
1543 self.wfile.write('<html><head>')
1544 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1545 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1546
1547 return True
1548
tony@chromium.org03266982010-03-05 03:18:42 +00001549 def MultipartHandler(self):
1550 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001551
tony@chromium.org4cb88302011-09-27 22:13:49 +00001552 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001553 if not self._ShouldHandleRequest(test_name):
1554 return False
1555
1556 num_frames = 10
1557 bound = '12345'
1558 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001559 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001560 'multipart/x-mixed-replace;boundary=' + bound)
1561 self.end_headers()
1562
1563 for i in xrange(num_frames):
1564 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001565 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001566 self.wfile.write('<title>page ' + str(i) + '</title>')
1567 self.wfile.write('page ' + str(i))
1568
1569 self.wfile.write('--' + bound + '--')
1570 return True
1571
tony@chromium.org4cb88302011-09-27 22:13:49 +00001572 def MultipartSlowHandler(self):
1573 """Send a multipart response (3 text/html pages) with a slight delay
1574 between each page. This is similar to how some pages show status using
1575 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001576
tony@chromium.org4cb88302011-09-27 22:13:49 +00001577 test_name = '/multipart-slow'
1578 if not self._ShouldHandleRequest(test_name):
1579 return False
1580
1581 num_frames = 3
1582 bound = '12345'
1583 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001584 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001585 'multipart/x-mixed-replace;boundary=' + bound)
1586 self.end_headers()
1587
1588 for i in xrange(num_frames):
1589 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001590 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001591 time.sleep(0.25)
1592 if i == 2:
1593 self.wfile.write('<title>PASS</title>')
1594 else:
1595 self.wfile.write('<title>page ' + str(i) + '</title>')
1596 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1597
1598 self.wfile.write('--' + bound + '--')
1599 return True
1600
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001601 def GetSSLSessionCacheHandler(self):
1602 """Send a reply containing a log of the session cache operations."""
1603
1604 if not self._ShouldHandleRequest('/ssl-session-cache'):
1605 return False
1606
1607 self.send_response(200)
1608 self.send_header('Content-Type', 'text/plain')
1609 self.end_headers()
1610 try:
1611 for (action, sessionID) in self.server.session_cache.log:
1612 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001613 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001614 self.wfile.write('Pass --https-record-resume in order to use' +
1615 ' this request')
1616 return True
1617
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001618 def CloseSocketHandler(self):
1619 """Closes the socket without sending anything."""
1620
1621 if not self._ShouldHandleRequest('/close-socket'):
1622 return False
1623
1624 self.wfile.close()
1625 return True
1626
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001627 def RangeResetHandler(self):
1628 """Send data broken up by connection resets every N (default 4K) bytes.
1629 Support range requests. If the data requested doesn't straddle a reset
1630 boundary, it will all be sent. Used for testing resuming downloads."""
1631
1632 if not self._ShouldHandleRequest('/rangereset'):
1633 return False
1634
1635 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1636
1637 # Defaults
1638 size = 8000
1639 # Note that the rst is sent just before sending the rst_boundary byte.
1640 rst_boundary = 4000
1641 respond_to_range = True
1642 hold_for_signal = False
1643
1644 # Parse the query
1645 qdict = urlparse.parse_qs(query, True)
1646 if 'size' in qdict:
1647 size = int(qdict['size'])
1648 if 'rst_boundary' in qdict:
1649 rst_boundary = int(qdict['rst_boundary'])
1650 if 'bounce_range' in qdict:
1651 respond_to_range = False
1652 if 'hold' in qdict:
1653 hold_for_signal = True
1654
1655 first_byte = 0
1656 last_byte = size - 1
1657
1658 # Does that define what we want to return, or do we need to apply
1659 # a range?
1660 range_response = False
1661 range_header = self.headers.getheader('range')
1662 if range_header and respond_to_range:
1663 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1664 if mo.group(1):
1665 first_byte = int(mo.group(1))
1666 if mo.group(2):
1667 last_byte = int(mo.group(2))
1668 if last_byte > size - 1:
1669 last_byte = size - 1
1670 range_response = True
1671 if last_byte < first_byte:
1672 return False
1673
1674 # Set socket send buf high enough that we don't need to worry
1675 # about asynchronous closes when sending RSTs.
1676 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF,
1677 16284)
1678
1679 if range_response:
1680 self.send_response(206)
1681 self.send_header('Content-Range',
1682 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1683 else:
1684 self.send_response(200)
1685 self.send_header('Content-Type', 'application/octet-stream')
1686 self.send_header('Content-Length', last_byte - first_byte + 1)
1687 self.end_headers()
1688
1689 if hold_for_signal:
1690 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1691 # a single byte, the self.server.handle_request() below hangs
1692 # without processing new incoming requests.
1693 self.wfile.write('X')
1694 first_byte = first_byte + 1
1695 # handle requests until one of them clears this flag.
1696 self.server.waitForDownload = True
1697 while self.server.waitForDownload:
1698 self.server.handle_request()
1699
1700 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
1701 if possible_rst >= last_byte:
1702 # No RST has been requested in this range, so we don't need to
1703 # do anything fancy; just write the data and let the python
1704 # infrastructure close the connection.
1705 self.wfile.write('X' * (last_byte - first_byte + 1))
1706 self.wfile.flush()
1707 return True
1708
1709 # We're resetting the connection part way in; go to the RST
1710 # boundary and then send an RST.
1711 # WINDOWS WARNING: On windows, if the amount of data sent before the
1712 # reset is > 4096, only 4096 bytes will make it across before the RST
1713 # despite the flush. This is hypothesized to be due to an underlying
1714 # asynchronous sending implementation, which the 0 second linger
1715 # forcibly terminates. The amount of data pre-RST should be kept below
1716 # 4096 for this reason.
1717 self.wfile.write('X' * (possible_rst - first_byte))
1718 self.wfile.flush()
1719 l_onoff = 1 # Linger is active.
1720 l_linger = 0 # Seconds to linger for.
1721 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1722 struct.pack('ii', l_onoff, l_linger))
1723
1724 # Close all duplicates of the underlying socket to force the RST.
1725 self.wfile.close()
1726 self.rfile.close()
1727 self.connection.close()
1728
1729 return True
1730
initial.commit94958cf2008-07-26 22:42:52 +00001731 def DefaultResponseHandler(self):
1732 """This is the catch-all response handler for requests that aren't handled
1733 by one of the special handlers above.
1734 Note that we specify the content-length as without it the https connection
1735 is not closed properly (and the browser keeps expecting data)."""
1736
1737 contents = "Default response given for path: " + self.path
1738 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001739 self.send_header('Content-Type', 'text/html')
1740 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001741 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001742 if (self.command != 'HEAD'):
1743 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001744 return True
1745
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001746 def RedirectConnectHandler(self):
1747 """Sends a redirect to the CONNECT request for www.redirect.com. This
1748 response is not specified by the RFC, so the browser should not follow
1749 the redirect."""
1750
1751 if (self.path.find("www.redirect.com") < 0):
1752 return False
1753
1754 dest = "http://www.destination.com/foo.js"
1755
1756 self.send_response(302) # moved temporarily
1757 self.send_header('Location', dest)
1758 self.send_header('Connection', 'close')
1759 self.end_headers()
1760 return True
1761
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001762 def ServerAuthConnectHandler(self):
1763 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1764 response doesn't make sense because the proxy server cannot request
1765 server authentication."""
1766
1767 if (self.path.find("www.server-auth.com") < 0):
1768 return False
1769
1770 challenge = 'Basic realm="WallyWorld"'
1771
1772 self.send_response(401) # unauthorized
1773 self.send_header('WWW-Authenticate', challenge)
1774 self.send_header('Connection', 'close')
1775 self.end_headers()
1776 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001777
1778 def DefaultConnectResponseHandler(self):
1779 """This is the catch-all response handler for CONNECT requests that aren't
1780 handled by one of the special handlers above. Real Web servers respond
1781 with 400 to CONNECT requests."""
1782
1783 contents = "Your client has issued a malformed or illegal request."
1784 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001785 self.send_header('Content-Type', 'text/html')
1786 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001787 self.end_headers()
1788 self.wfile.write(contents)
1789 return True
1790
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001791 def DeviceManagementHandler(self):
1792 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001793
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001794 if not self._ShouldHandleRequest("/device_management"):
1795 return False
1796
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001797 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001798
1799 if not self.server._device_management_handler:
1800 import device_management
1801 policy_path = os.path.join(self.server.data_dir, 'device_management')
1802 self.server._device_management_handler = (
joaodasilva@chromium.org3c069da2012-11-20 16:17:15 +00001803 device_management.TestServer(policy_path, self.server.policy_keys))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001804
1805 http_response, raw_reply = (
1806 self.server._device_management_handler.HandleRequest(self.path,
1807 self.headers,
1808 raw_request))
1809 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001810 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001811 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001812 self.end_headers()
1813 self.wfile.write(raw_reply)
1814 return True
1815
initial.commit94958cf2008-07-26 22:42:52 +00001816 # called by the redirect handling function when there is no parameter
1817 def sendRedirectHelp(self, redirect_name):
1818 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001819 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001820 self.end_headers()
1821 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1822 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1823 self.wfile.write('</body></html>')
1824
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001825 # called by chunked handling function
1826 def sendChunkHelp(self, chunk):
1827 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1828 self.wfile.write('%X\r\n' % len(chunk))
1829 self.wfile.write(chunk)
1830 self.wfile.write('\r\n')
1831
akalin@chromium.org154bb132010-11-12 02:20:27 +00001832
1833class SyncPageHandler(BasePageHandler):
1834 """Handler for the main HTTP sync server."""
1835
1836 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001837 get_handlers = [self.ChromiumSyncTimeHandler,
1838 self.ChromiumSyncMigrationOpHandler,
1839 self.ChromiumSyncCredHandler,
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001840 self.ChromiumSyncXmppCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001841 self.ChromiumSyncDisableNotificationsOpHandler,
1842 self.ChromiumSyncEnableNotificationsOpHandler,
1843 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001844 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001845 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001846 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001847 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001848 self.ChromiumSyncCreateSyncedBookmarksOpHandler,
zea@chromium.org261440b2012-12-19 01:56:52 +00001849 self.ChromiumSyncEnableKeystoreEncryptionOpHandler,
1850 self.ChromiumSyncRotateKeystoreKeysOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001851
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001852 post_handlers = [self.ChromiumSyncCommandHandler,
1853 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001854 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001855 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001856 post_handlers, [])
1857
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001858
akalin@chromium.org154bb132010-11-12 02:20:27 +00001859 def ChromiumSyncTimeHandler(self):
1860 """Handle Chromium sync .../time requests.
1861
1862 The syncer sometimes checks server reachability by examining /time.
1863 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001864
akalin@chromium.org154bb132010-11-12 02:20:27 +00001865 test_name = "/chromiumsync/time"
1866 if not self._ShouldHandleRequest(test_name):
1867 return False
1868
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001869 # Chrome hates it if we send a response before reading the request.
1870 if self.headers.getheader('content-length'):
1871 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001872 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001873
akalin@chromium.org154bb132010-11-12 02:20:27 +00001874 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001875 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001876 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001877 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001878 return True
1879
1880 def ChromiumSyncCommandHandler(self):
1881 """Handle a chromiumsync command arriving via http.
1882
1883 This covers all sync protocol commands: authentication, getupdates, and
1884 commit.
1885 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001886
akalin@chromium.org154bb132010-11-12 02:20:27 +00001887 test_name = "/chromiumsync/command"
1888 if not self._ShouldHandleRequest(test_name):
1889 return False
1890
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001891 length = int(self.headers.getheader('content-length'))
1892 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001893 http_response = 200
1894 raw_reply = None
1895 if not self.server.GetAuthenticated():
1896 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001897 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1898 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001899 else:
1900 http_response, raw_reply = self.server.HandleCommand(
1901 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001902
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001903 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001904 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001905 if http_response == 401:
1906 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001907 self.end_headers()
1908 self.wfile.write(raw_reply)
1909 return True
1910
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001911 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001912 test_name = "/chromiumsync/migrate"
1913 if not self._ShouldHandleRequest(test_name):
1914 return False
1915
1916 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1917 self.path)
1918 self.send_response(http_response)
1919 self.send_header('Content-Type', 'text/html')
1920 self.send_header('Content-Length', len(raw_reply))
1921 self.end_headers()
1922 self.wfile.write(raw_reply)
1923 return True
1924
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001925 def ChromiumSyncCredHandler(self):
1926 test_name = "/chromiumsync/cred"
1927 if not self._ShouldHandleRequest(test_name):
1928 return False
1929 try:
1930 query = urlparse.urlparse(self.path)[4]
1931 cred_valid = urlparse.parse_qs(query)['valid']
1932 if cred_valid[0] == 'True':
1933 self.server.SetAuthenticated(True)
1934 else:
1935 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001936 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001937 self.server.SetAuthenticated(False)
1938
1939 http_response = 200
1940 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1941 self.send_response(http_response)
1942 self.send_header('Content-Type', 'text/html')
1943 self.send_header('Content-Length', len(raw_reply))
1944 self.end_headers()
1945 self.wfile.write(raw_reply)
1946 return True
1947
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001948 def ChromiumSyncXmppCredHandler(self):
1949 test_name = "/chromiumsync/xmppcred"
1950 if not self._ShouldHandleRequest(test_name):
1951 return False
1952 xmpp_server = self.server.GetXmppServer()
1953 try:
1954 query = urlparse.urlparse(self.path)[4]
1955 cred_valid = urlparse.parse_qs(query)['valid']
1956 if cred_valid[0] == 'True':
1957 xmpp_server.SetAuthenticated(True)
1958 else:
1959 xmpp_server.SetAuthenticated(False)
1960 except:
1961 xmpp_server.SetAuthenticated(False)
1962
1963 http_response = 200
1964 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated()
1965 self.send_response(http_response)
1966 self.send_header('Content-Type', 'text/html')
1967 self.send_header('Content-Length', len(raw_reply))
1968 self.end_headers()
1969 self.wfile.write(raw_reply)
1970 return True
1971
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001972 def ChromiumSyncDisableNotificationsOpHandler(self):
1973 test_name = "/chromiumsync/disablenotifications"
1974 if not self._ShouldHandleRequest(test_name):
1975 return False
1976 self.server.GetXmppServer().DisableNotifications()
1977 result = 200
1978 raw_reply = ('<html><title>Notifications disabled</title>'
1979 '<H1>Notifications disabled</H1></html>')
1980 self.send_response(result)
1981 self.send_header('Content-Type', 'text/html')
1982 self.send_header('Content-Length', len(raw_reply))
1983 self.end_headers()
1984 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001985 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001986
1987 def ChromiumSyncEnableNotificationsOpHandler(self):
1988 test_name = "/chromiumsync/enablenotifications"
1989 if not self._ShouldHandleRequest(test_name):
1990 return False
1991 self.server.GetXmppServer().EnableNotifications()
1992 result = 200
1993 raw_reply = ('<html><title>Notifications enabled</title>'
1994 '<H1>Notifications enabled</H1></html>')
1995 self.send_response(result)
1996 self.send_header('Content-Type', 'text/html')
1997 self.send_header('Content-Length', len(raw_reply))
1998 self.end_headers()
1999 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002000 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00002001
2002 def ChromiumSyncSendNotificationOpHandler(self):
2003 test_name = "/chromiumsync/sendnotification"
2004 if not self._ShouldHandleRequest(test_name):
2005 return False
2006 query = urlparse.urlparse(self.path)[4]
2007 query_params = urlparse.parse_qs(query)
2008 channel = ''
2009 data = ''
2010 if 'channel' in query_params:
2011 channel = query_params['channel'][0]
2012 if 'data' in query_params:
2013 data = query_params['data'][0]
2014 self.server.GetXmppServer().SendNotification(channel, data)
2015 result = 200
2016 raw_reply = ('<html><title>Notification sent</title>'
2017 '<H1>Notification sent with channel "%s" '
2018 'and data "%s"</H1></html>'
2019 % (channel, data))
2020 self.send_response(result)
2021 self.send_header('Content-Type', 'text/html')
2022 self.send_header('Content-Length', len(raw_reply))
2023 self.end_headers()
2024 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002025 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00002026
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00002027 def ChromiumSyncBirthdayErrorOpHandler(self):
2028 test_name = "/chromiumsync/birthdayerror"
2029 if not self._ShouldHandleRequest(test_name):
2030 return False
2031 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
2032 self.send_response(result)
2033 self.send_header('Content-Type', 'text/html')
2034 self.send_header('Content-Length', len(raw_reply))
2035 self.end_headers()
2036 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002037 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00002038
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00002039 def ChromiumSyncTransientErrorOpHandler(self):
2040 test_name = "/chromiumsync/transienterror"
2041 if not self._ShouldHandleRequest(test_name):
2042 return False
2043 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
2044 self.send_response(result)
2045 self.send_header('Content-Type', 'text/html')
2046 self.send_header('Content-Length', len(raw_reply))
2047 self.end_headers()
2048 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002049 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00002050
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00002051 def ChromiumSyncErrorOpHandler(self):
2052 test_name = "/chromiumsync/error"
2053 if not self._ShouldHandleRequest(test_name):
2054 return False
2055 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
2056 self.path)
2057 self.send_response(result)
2058 self.send_header('Content-Type', 'text/html')
2059 self.send_header('Content-Length', len(raw_reply))
2060 self.end_headers()
2061 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002062 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00002063
nyquist@chromium.org154389d2012-09-07 20:33:58 +00002064 def ChromiumSyncSyncTabFaviconsOpHandler(self):
2065 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00002066 if not self._ShouldHandleRequest(test_name):
2067 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00002068 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00002069 self.send_response(result)
2070 self.send_header('Content-Type', 'text/html')
2071 self.send_header('Content-Length', len(raw_reply))
2072 self.end_headers()
2073 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002074 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00002075
zea@chromium.orga606cbb2012-03-16 04:24:18 +00002076 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
2077 test_name = "/chromiumsync/createsyncedbookmarks"
2078 if not self._ShouldHandleRequest(test_name):
2079 return False
2080 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
2081 self.send_response(result)
2082 self.send_header('Content-Type', 'text/html')
2083 self.send_header('Content-Length', len(raw_reply))
2084 self.end_headers()
2085 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002086 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00002087
zea@chromium.orgbb26c702012-11-15 02:43:40 +00002088 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
2089 test_name = "/chromiumsync/enablekeystoreencryption"
2090 if not self._ShouldHandleRequest(test_name):
2091 return False
2092 result, raw_reply = (
2093 self.server._sync_handler.HandleEnableKeystoreEncryption())
2094 self.send_response(result)
2095 self.send_header('Content-Type', 'text/html')
2096 self.send_header('Content-Length', len(raw_reply))
2097 self.end_headers()
2098 self.wfile.write(raw_reply)
2099 return True
2100
zea@chromium.org261440b2012-12-19 01:56:52 +00002101 def ChromiumSyncRotateKeystoreKeysOpHandler(self):
2102 test_name = "/chromiumsync/rotatekeystorekeys"
2103 if not self._ShouldHandleRequest(test_name):
2104 return False
2105 result, raw_reply = (
2106 self.server._sync_handler.HandleRotateKeystoreKeys())
2107 self.send_response(result)
2108 self.send_header('Content-Type', 'text/html')
2109 self.send_header('Content-Length', len(raw_reply))
2110 self.end_headers()
2111 self.wfile.write(raw_reply)
2112 return True
2113
akalin@chromium.org154bb132010-11-12 02:20:27 +00002114
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002115class OCSPHandler(BasePageHandler):
2116 def __init__(self, request, client_address, socket_server):
2117 handlers = [self.OCSPResponse]
2118 self.ocsp_response = socket_server.ocsp_response
2119 BasePageHandler.__init__(self, request, client_address, socket_server,
2120 [], handlers, [], handlers, [])
2121
2122 def OCSPResponse(self):
2123 self.send_response(200)
2124 self.send_header('Content-Type', 'application/ocsp-response')
2125 self.send_header('Content-Length', str(len(self.ocsp_response)))
2126 self.end_headers()
2127
2128 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002129
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002131class TCPEchoHandler(SocketServer.BaseRequestHandler):
2132 """The RequestHandler class for TCP echo server.
2133
2134 It is instantiated once per connection to the server, and overrides the
2135 handle() method to implement communication to the client.
2136 """
2137
2138 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002139 """Handles the request from the client and constructs a response."""
2140
2141 data = self.request.recv(65536).strip()
2142 # Verify the "echo request" message received from the client. Send back
2143 # "echo response" message if "echo request" message is valid.
2144 try:
2145 return_data = echo_message.GetEchoResponseData(data)
2146 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002147 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002148 except ValueError:
2149 return
2150
2151 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002152
2153
2154class UDPEchoHandler(SocketServer.BaseRequestHandler):
2155 """The RequestHandler class for UDP echo server.
2156
2157 It is instantiated once per connection to the server, and overrides the
2158 handle() method to implement communication to the client.
2159 """
2160
2161 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002162 """Handles the request from the client and constructs a response."""
2163
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002164 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002165 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002166 # Verify the "echo request" message received from the client. Send back
2167 # "echo response" message if "echo request" message is valid.
2168 try:
2169 return_data = echo_message.GetEchoResponseData(data)
2170 if not return_data:
2171 return
2172 except ValueError:
2173 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002174 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002175
2176
bashi@chromium.org33233532012-09-08 17:37:24 +00002177class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2178 """A request handler that behaves as a proxy server which requires
2179 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2180 """
2181
2182 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2183
2184 def parse_request(self):
2185 """Overrides parse_request to check credential."""
2186
2187 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2188 return False
2189
2190 auth = self.headers.getheader('Proxy-Authorization')
2191 if auth != self._AUTH_CREDENTIAL:
2192 self.send_response(407)
2193 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2194 self.end_headers()
2195 return False
2196
2197 return True
2198
2199 def _start_read_write(self, sock):
2200 sock.setblocking(0)
2201 self.request.setblocking(0)
2202 rlist = [self.request, sock]
2203 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002204 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002205 if errors:
2206 self.send_response(500)
2207 self.end_headers()
2208 return
2209 for s in ready_sockets:
2210 received = s.recv(1024)
2211 if len(received) == 0:
2212 return
2213 if s == self.request:
2214 other = sock
2215 else:
2216 other = self.request
2217 other.send(received)
2218
2219 def _do_common_method(self):
2220 url = urlparse.urlparse(self.path)
2221 port = url.port
2222 if not port:
2223 if url.scheme == 'http':
2224 port = 80
2225 elif url.scheme == 'https':
2226 port = 443
2227 if not url.hostname or not port:
2228 self.send_response(400)
2229 self.end_headers()
2230 return
2231
2232 if len(url.path) == 0:
2233 path = '/'
2234 else:
2235 path = url.path
2236 if len(url.query) > 0:
2237 path = '%s?%s' % (url.path, url.query)
2238
2239 sock = None
2240 try:
2241 sock = socket.create_connection((url.hostname, port))
2242 sock.send('%s %s %s\r\n' % (
2243 self.command, path, self.protocol_version))
2244 for header in self.headers.headers:
2245 header = header.strip()
2246 if (header.lower().startswith('connection') or
2247 header.lower().startswith('proxy')):
2248 continue
2249 sock.send('%s\r\n' % header)
2250 sock.send('\r\n')
2251 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002252 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002253 self.send_response(500)
2254 self.end_headers()
2255 finally:
2256 if sock is not None:
2257 sock.close()
2258
2259 def do_CONNECT(self):
2260 try:
2261 pos = self.path.rfind(':')
2262 host = self.path[:pos]
2263 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002264 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002265 self.send_response(400)
2266 self.end_headers()
2267
2268 try:
2269 sock = socket.create_connection((host, port))
2270 self.send_response(200, 'Connection established')
2271 self.end_headers()
2272 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002273 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002274 self.send_response(500)
2275 self.end_headers()
2276 finally:
2277 sock.close()
2278
2279 def do_GET(self):
2280 self._do_common_method()
2281
2282 def do_HEAD(self):
2283 self._do_common_method()
2284
2285
mattm@chromium.org830a3712012-11-07 23:00:07 +00002286class ServerRunner(testserver_base.TestServerRunner):
2287 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002288
mattm@chromium.org830a3712012-11-07 23:00:07 +00002289 def __init__(self):
2290 super(ServerRunner, self).__init__()
2291 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002292
mattm@chromium.org830a3712012-11-07 23:00:07 +00002293 def __make_data_dir(self):
2294 if self.options.data_dir:
2295 if not os.path.isdir(self.options.data_dir):
2296 raise testserver_base.OptionError('specified data dir not found: ' +
2297 self.options.data_dir + ' exiting...')
2298 my_data_dir = self.options.data_dir
2299 else:
2300 # Create the default path to our data dir, relative to the exe dir.
2301 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2302 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002303
mattm@chromium.org830a3712012-11-07 23:00:07 +00002304 #TODO(ibrar): Must use Find* funtion defined in google\tools
2305 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002306
mattm@chromium.org830a3712012-11-07 23:00:07 +00002307 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002308
mattm@chromium.org830a3712012-11-07 23:00:07 +00002309 def create_server(self, server_data):
2310 port = self.options.port
2311 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002312
mattm@chromium.org830a3712012-11-07 23:00:07 +00002313 if self.options.server_type == SERVER_HTTP:
2314 if self.options.https:
2315 pem_cert_and_key = None
2316 if self.options.cert_and_key_file:
2317 if not os.path.isfile(self.options.cert_and_key_file):
2318 raise testserver_base.OptionError(
2319 'specified server cert file not found: ' +
2320 self.options.cert_and_key_file + ' exiting...')
2321 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002322 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002323 # generate a new certificate and run an OCSP server for it.
2324 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2325 print ('OCSP server started on %s:%d...' %
2326 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002327
mattm@chromium.org830a3712012-11-07 23:00:07 +00002328 ocsp_der = None
2329 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002330
mattm@chromium.org830a3712012-11-07 23:00:07 +00002331 if self.options.ocsp == 'ok':
2332 ocsp_state = minica.OCSP_STATE_GOOD
2333 elif self.options.ocsp == 'revoked':
2334 ocsp_state = minica.OCSP_STATE_REVOKED
2335 elif self.options.ocsp == 'invalid':
2336 ocsp_state = minica.OCSP_STATE_INVALID
2337 elif self.options.ocsp == 'unauthorized':
2338 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2339 elif self.options.ocsp == 'unknown':
2340 ocsp_state = minica.OCSP_STATE_UNKNOWN
2341 else:
2342 raise testserver_base.OptionError('unknown OCSP status: ' +
2343 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002344
mattm@chromium.org830a3712012-11-07 23:00:07 +00002345 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2346 subject = "127.0.0.1",
2347 ocsp_url = ("http://%s:%d/ocsp" %
2348 (host, self.__ocsp_server.server_port)),
2349 ocsp_state = ocsp_state)
2350
2351 self.__ocsp_server.ocsp_response = ocsp_der
2352
2353 for ca_cert in self.options.ssl_client_ca:
2354 if not os.path.isfile(ca_cert):
2355 raise testserver_base.OptionError(
2356 'specified trusted client CA file not found: ' + ca_cert +
2357 ' exiting...')
2358 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2359 self.options.ssl_client_auth,
2360 self.options.ssl_client_ca,
2361 self.options.ssl_bulk_cipher,
2362 self.options.record_resume,
2363 self.options.tls_intolerant)
2364 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2365 else:
2366 server = HTTPServer((host, port), TestPageHandler)
2367 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2368
2369 server.data_dir = self.__make_data_dir()
2370 server.file_root_url = self.options.file_root_url
2371 server_data['port'] = server.server_port
2372 server._device_management_handler = None
2373 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00002374 elif self.options.server_type == SERVER_WEBSOCKET:
2375 # Launch pywebsocket via WebSocketServer.
2376 logger = logging.getLogger()
2377 logger.addHandler(logging.StreamHandler())
2378 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2379 # is required to work correctly. It should be fixed from pywebsocket side.
2380 os.chdir(self.__make_data_dir())
2381 websocket_options = WebSocketOptions(host, port, '.')
2382 if self.options.cert_and_key_file:
2383 websocket_options.use_tls = True
2384 websocket_options.private_key = self.options.cert_and_key_file
2385 websocket_options.certificate = self.options.cert_and_key_file
2386 if self.options.ssl_client_auth:
2387 websocket_options.tls_client_auth = True
2388 if len(self.options.ssl_client_ca) != 1:
2389 raise testserver_base.OptionError(
2390 'one trusted client CA file should be specified')
2391 if not os.path.isfile(self.options.ssl_client_ca[0]):
2392 raise testserver_base.OptionError(
2393 'specified trusted client CA file not found: ' +
2394 self.options.ssl_client_ca[0] + ' exiting...')
2395 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2396 server = WebSocketServer(websocket_options)
2397 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2398 server_data['port'] = server.server_port
2399 elif self.options.server_type == SERVER_SYNC:
2400 xmpp_port = self.options.xmpp_port
2401 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2402 print 'Sync HTTP server started on port %d...' % server.server_port
2403 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2404 server_data['port'] = server.server_port
2405 server_data['xmpp_port'] = server.xmpp_port
2406 elif self.options.server_type == SERVER_TCP_ECHO:
2407 # Used for generating the key (randomly) that encodes the "echo request"
2408 # message.
2409 random.seed()
2410 server = TCPEchoServer((host, port), TCPEchoHandler)
2411 print 'Echo TCP server started on port %d...' % server.server_port
2412 server_data['port'] = server.server_port
2413 elif self.options.server_type == SERVER_UDP_ECHO:
2414 # Used for generating the key (randomly) that encodes the "echo request"
2415 # message.
2416 random.seed()
2417 server = UDPEchoServer((host, port), UDPEchoHandler)
2418 print 'Echo UDP server started on port %d...' % server.server_port
2419 server_data['port'] = server.server_port
2420 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2421 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2422 print 'BasicAuthProxy server started on port %d...' % server.server_port
2423 server_data['port'] = server.server_port
2424 elif self.options.server_type == SERVER_FTP:
2425 my_data_dir = self.__make_data_dir()
2426
2427 # Instantiate a dummy authorizer for managing 'virtual' users
2428 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2429
2430 # Define a new user having full r/w permissions and a read-only
2431 # anonymous user
2432 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2433
2434 authorizer.add_anonymous(my_data_dir)
2435
2436 # Instantiate FTP handler class
2437 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2438 ftp_handler.authorizer = authorizer
2439
2440 # Define a customized banner (string returned when client connects)
2441 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2442 pyftpdlib.ftpserver.__ver__)
2443
2444 # Instantiate FTP server class and listen to address:port
2445 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2446 server_data['port'] = server.socket.getsockname()[1]
2447 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002448 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002449 raise testserver_base.OptionError('unknown server type' +
2450 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002451
mattm@chromium.org830a3712012-11-07 23:00:07 +00002452 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002453
mattm@chromium.org830a3712012-11-07 23:00:07 +00002454 def run_server(self):
2455 if self.__ocsp_server:
2456 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002457
mattm@chromium.org830a3712012-11-07 23:00:07 +00002458 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002459
mattm@chromium.org830a3712012-11-07 23:00:07 +00002460 if self.__ocsp_server:
2461 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002462
mattm@chromium.org830a3712012-11-07 23:00:07 +00002463 def add_options(self):
2464 testserver_base.TestServerRunner.add_options(self)
2465 self.option_parser.add_option('-f', '--ftp', action='store_const',
2466 const=SERVER_FTP, default=SERVER_HTTP,
2467 dest='server_type',
2468 help='start up an FTP server.')
2469 self.option_parser.add_option('--sync', action='store_const',
2470 const=SERVER_SYNC, default=SERVER_HTTP,
2471 dest='server_type',
2472 help='start up a sync server.')
2473 self.option_parser.add_option('--tcp-echo', action='store_const',
2474 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2475 dest='server_type',
2476 help='start up a tcp echo server.')
2477 self.option_parser.add_option('--udp-echo', action='store_const',
2478 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2479 dest='server_type',
2480 help='start up a udp echo server.')
2481 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2482 const=SERVER_BASIC_AUTH_PROXY,
2483 default=SERVER_HTTP, dest='server_type',
2484 help='start up a proxy server which requires '
2485 'basic authentication.')
2486 self.option_parser.add_option('--websocket', action='store_const',
2487 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2488 dest='server_type',
2489 help='start up a WebSocket server.')
2490 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2491 help='Port used by the XMPP server. If '
2492 'unspecified, the XMPP server will listen on '
2493 'an ephemeral port.')
2494 self.option_parser.add_option('--data-dir', dest='data_dir',
2495 help='Directory from which to read the '
2496 'files.')
2497 self.option_parser.add_option('--https', action='store_true',
2498 dest='https', help='Specify that https '
2499 'should be used.')
2500 self.option_parser.add_option('--cert-and-key-file',
2501 dest='cert_and_key_file', help='specify the '
2502 'path to the file containing the certificate '
2503 'and private key for the server in PEM '
2504 'format')
2505 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2506 help='The type of OCSP response generated '
2507 'for the automatically generated '
2508 'certificate. One of [ok,revoked,invalid]')
2509 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2510 default='0', type='int',
2511 help='If nonzero, certain TLS connections '
2512 'will be aborted in order to test version '
2513 'fallback. 1 means all TLS versions will be '
2514 'aborted. 2 means TLS 1.1 or higher will be '
2515 'aborted. 3 means TLS 1.2 or higher will be '
2516 'aborted.')
2517 self.option_parser.add_option('--https-record-resume',
2518 dest='record_resume', const=True,
2519 default=False, action='store_const',
2520 help='Record resumption cache events rather '
2521 'than resuming as normal. Allows the use of '
2522 'the /ssl-session-cache request')
2523 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2524 help='Require SSL client auth on every '
2525 'connection.')
2526 self.option_parser.add_option('--ssl-client-ca', action='append',
2527 default=[], help='Specify that the client '
2528 'certificate request should include the CA '
2529 'named in the subject of the DER-encoded '
2530 'certificate contained in the specified '
2531 'file. This option may appear multiple '
2532 'times, indicating multiple CA names should '
2533 'be sent in the request.')
2534 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2535 help='Specify the bulk encryption '
2536 'algorithm(s) that will be accepted by the '
2537 'SSL server. Valid values are "aes256", '
2538 '"aes128", "3des", "rc4". If omitted, all '
2539 'algorithms will be used. This option may '
2540 'appear multiple times, indicating '
2541 'multiple algorithms should be enabled.');
2542 self.option_parser.add_option('--file-root-url', default='/files/',
2543 help='Specify a root URL for files served.')
2544 self.option_parser.add_option('--policy-key', action='append',
2545 dest='policy_keys',
2546 help='Specify a path to a PEM-encoded '
2547 'private key to use for policy signing. May '
2548 'be specified multiple times in order to '
2549 'load multipe keys into the server. If the '
2550 'server has multiple keys, it will rotate '
2551 'through them in at each request a '
2552 'round-robin fashion. The server will '
2553 'generate a random key if none is specified '
2554 'on the command line.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002555
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002556
initial.commit94958cf2008-07-26 22:42:52 +00002557if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002558 sys.exit(ServerRunner().main())