blob: 4f97587504500ab8879742bd9b55f06613cca510 [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
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000022import httplib
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000023import json
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000024import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000025import minica
initial.commit94958cf2008-07-26 22:42:52 +000026import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000027import random
initial.commit94958cf2008-07-26 22:42:52 +000028import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000029import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000030import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import SocketServer
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,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000460 self.GDataAuthHandler,
461 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000462 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000463 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000464 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000465 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000466 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000467 self.AuthBasicHandler,
468 self.AuthDigestHandler,
469 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000470 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000471 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000472 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000473 self.ServerRedirectHandler,
474 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000475 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000476 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000477 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000478 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000479 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000480 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000482 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000483 self.DeviceManagementHandler,
484 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000485 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000486 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000487 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000488 head_handlers = [
489 self.FileHandler,
490 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000491
maruel@google.come250a9b2009-03-10 17:39:46 +0000492 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000493 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000494 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000495 'gif': 'image/gif',
496 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000497 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000498 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000499 'pdf' : 'application/pdf',
500 '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
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001313 def GDataAuthHandler(self):
1314 """This handler verifies the Authentication header for GData requests."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001315
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001316 if not self.server.gdata_auth_token:
1317 # --auth-token is not specified, not the test case for GData.
1318 return False
1319
1320 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1321 return False
1322
1323 if 'GData-Version' not in self.headers:
1324 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1325 return True
1326
1327 if 'Authorization' not in self.headers:
1328 self.send_error(httplib.UNAUTHORIZED)
1329 return True
1330
1331 field_prefix = 'Bearer '
1332 authorization = self.headers['Authorization']
1333 if not authorization.startswith(field_prefix):
1334 self.send_error(httplib.UNAUTHORIZED)
1335 return True
1336
1337 code = authorization[len(field_prefix):]
1338 if code != self.server.gdata_auth_token:
1339 self.send_error(httplib.UNAUTHORIZED)
1340 return True
1341
1342 return False
1343
1344 def GDataDocumentsFeedQueryHandler(self):
1345 """This handler verifies if required parameters are properly
1346 specified for the GData DocumentsFeed request."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001347
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001348 if not self.server.gdata_auth_token:
1349 # --auth-token is not specified, not the test case for GData.
1350 return False
1351
1352 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1353 return False
1354
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001355 (_path, _question, query_params) = self.path.partition('?')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001356 self.query_params = urlparse.parse_qs(query_params)
1357
1358 if 'v' not in self.query_params:
1359 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1360 return True
1361 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1362 # currently our GData client only uses JSON format.
1363 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1364 return True
1365
1366 return False
1367
tonyg@chromium.org75054202010-03-31 22:06:10 +00001368 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001369 """Returns a nonce that's stable per request path for the server's lifetime.
1370 This is a fake implementation. A real implementation would only use a given
1371 nonce a single time (hence the name n-once). However, for the purposes of
1372 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001373
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001374 Args:
1375 force_reset: Iff set, the nonce will be changed. Useful for testing the
1376 "stale" response.
1377 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001378
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001379 if force_reset or not self.server.nonce_time:
1380 self.server.nonce_time = time.time()
1381 return hashlib.md5('privatekey%s%d' %
1382 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001383
1384 def AuthDigestHandler(self):
1385 """This handler tests 'Digest' authentication.
1386
1387 It just sends a page with title 'user/pass' if you succeed.
1388
1389 A stale response is sent iff "stale" is present in the request path.
1390 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001391
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001392 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001393 return False
1394
tonyg@chromium.org75054202010-03-31 22:06:10 +00001395 stale = 'stale' in self.path
1396 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001397 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001398 password = 'secret'
1399 realm = 'testrealm'
1400
1401 auth = self.headers.getheader('authorization')
1402 pairs = {}
1403 try:
1404 if not auth:
1405 raise Exception('no auth')
1406 if not auth.startswith('Digest'):
1407 raise Exception('not digest')
1408 # Pull out all the name="value" pairs as a dictionary.
1409 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1410
1411 # Make sure it's all valid.
1412 if pairs['nonce'] != nonce:
1413 raise Exception('wrong nonce')
1414 if pairs['opaque'] != opaque:
1415 raise Exception('wrong opaque')
1416
1417 # Check the 'response' value and make sure it matches our magic hash.
1418 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001419 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001420 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001421 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001422 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001423 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001424 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1425 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001426 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001427
1428 if pairs['response'] != response:
1429 raise Exception('wrong password')
1430 except Exception, e:
1431 # Authentication failed.
1432 self.send_response(401)
1433 hdr = ('Digest '
1434 'realm="%s", '
1435 'domain="/", '
1436 'qop="auth", '
1437 'algorithm=MD5, '
1438 'nonce="%s", '
1439 'opaque="%s"') % (realm, nonce, opaque)
1440 if stale:
1441 hdr += ', stale="TRUE"'
1442 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001443 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001444 self.end_headers()
1445 self.wfile.write('<html><head>')
1446 self.wfile.write('<title>Denied: %s</title>' % e)
1447 self.wfile.write('</head><body>')
1448 self.wfile.write('auth=%s<p>' % auth)
1449 self.wfile.write('pairs=%s<p>' % pairs)
1450 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1451 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1452 self.wfile.write('</body></html>')
1453 return True
1454
1455 # Authentication successful.
1456 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001457 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001458 self.end_headers()
1459 self.wfile.write('<html><head>')
1460 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1461 self.wfile.write('</head><body>')
1462 self.wfile.write('auth=%s<p>' % auth)
1463 self.wfile.write('pairs=%s<p>' % pairs)
1464 self.wfile.write('</body></html>')
1465
1466 return True
1467
1468 def SlowServerHandler(self):
1469 """Wait for the user suggested time before responding. The syntax is
1470 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001471
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001472 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001473 return False
1474 query_char = self.path.find('?')
1475 wait_sec = 1.0
1476 if query_char >= 0:
1477 try:
1478 wait_sec = int(self.path[query_char + 1:])
1479 except ValueError:
1480 pass
1481 time.sleep(wait_sec)
1482 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001483 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001484 self.end_headers()
1485 self.wfile.write("waited %d seconds" % wait_sec)
1486 return True
1487
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001488 def ChunkedServerHandler(self):
1489 """Send chunked response. Allows to specify chunks parameters:
1490 - waitBeforeHeaders - ms to wait before sending headers
1491 - waitBetweenChunks - ms to wait between chunks
1492 - chunkSize - size of each chunk in bytes
1493 - chunksNumber - number of chunks
1494 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1495 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001496
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001497 if not self._ShouldHandleRequest("/chunked"):
1498 return False
1499 query_char = self.path.find('?')
1500 chunkedSettings = {'waitBeforeHeaders' : 0,
1501 'waitBetweenChunks' : 0,
1502 'chunkSize' : 5,
1503 'chunksNumber' : 5}
1504 if query_char >= 0:
1505 params = self.path[query_char + 1:].split('&')
1506 for param in params:
1507 keyValue = param.split('=')
1508 if len(keyValue) == 2:
1509 try:
1510 chunkedSettings[keyValue[0]] = int(keyValue[1])
1511 except ValueError:
1512 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001513 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001514 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1515 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001516 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001517 self.send_header('Connection', 'close')
1518 self.send_header('Transfer-Encoding', 'chunked')
1519 self.end_headers()
1520 # Chunked encoding: sending all chunks, then final zero-length chunk and
1521 # then final CRLF.
1522 for i in range(0, chunkedSettings['chunksNumber']):
1523 if i > 0:
1524 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1525 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001526 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001527 self.sendChunkHelp('')
1528 return True
1529
initial.commit94958cf2008-07-26 22:42:52 +00001530 def ContentTypeHandler(self):
1531 """Returns a string of html with the given content type. E.g.,
1532 /contenttype?text/css returns an html file with the Content-Type
1533 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001534
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001535 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001536 return False
1537 query_char = self.path.find('?')
1538 content_type = self.path[query_char + 1:].strip()
1539 if not content_type:
1540 content_type = 'text/html'
1541 self.send_response(200)
1542 self.send_header('Content-Type', content_type)
1543 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001544 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001545 return True
1546
creis@google.com2f4f6a42011-03-25 19:44:19 +00001547 def NoContentHandler(self):
1548 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001549
creis@google.com2f4f6a42011-03-25 19:44:19 +00001550 if not self._ShouldHandleRequest("/nocontent"):
1551 return False
1552 self.send_response(204)
1553 self.end_headers()
1554 return True
1555
initial.commit94958cf2008-07-26 22:42:52 +00001556 def ServerRedirectHandler(self):
1557 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001558 '/server-redirect?http://foo.bar/asdf' to redirect to
1559 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001560
1561 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001562 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001563 return False
1564
1565 query_char = self.path.find('?')
1566 if query_char < 0 or len(self.path) <= query_char + 1:
1567 self.sendRedirectHelp(test_name)
1568 return True
1569 dest = self.path[query_char + 1:]
1570
1571 self.send_response(301) # moved permanently
1572 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001573 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001574 self.end_headers()
1575 self.wfile.write('<html><head>')
1576 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1577
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001578 return True
initial.commit94958cf2008-07-26 22:42:52 +00001579
1580 def ClientRedirectHandler(self):
1581 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001582 '/client-redirect?http://foo.bar/asdf' to redirect to
1583 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001584
1585 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001586 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001587 return False
1588
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001589 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001590 if query_char < 0 or len(self.path) <= query_char + 1:
1591 self.sendRedirectHelp(test_name)
1592 return True
1593 dest = self.path[query_char + 1:]
1594
1595 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001596 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001597 self.end_headers()
1598 self.wfile.write('<html><head>')
1599 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1600 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1601
1602 return True
1603
tony@chromium.org03266982010-03-05 03:18:42 +00001604 def MultipartHandler(self):
1605 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001606
tony@chromium.org4cb88302011-09-27 22:13:49 +00001607 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001608 if not self._ShouldHandleRequest(test_name):
1609 return False
1610
1611 num_frames = 10
1612 bound = '12345'
1613 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001614 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001615 'multipart/x-mixed-replace;boundary=' + bound)
1616 self.end_headers()
1617
1618 for i in xrange(num_frames):
1619 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001620 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001621 self.wfile.write('<title>page ' + str(i) + '</title>')
1622 self.wfile.write('page ' + str(i))
1623
1624 self.wfile.write('--' + bound + '--')
1625 return True
1626
tony@chromium.org4cb88302011-09-27 22:13:49 +00001627 def MultipartSlowHandler(self):
1628 """Send a multipart response (3 text/html pages) with a slight delay
1629 between each page. This is similar to how some pages show status using
1630 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001631
tony@chromium.org4cb88302011-09-27 22:13:49 +00001632 test_name = '/multipart-slow'
1633 if not self._ShouldHandleRequest(test_name):
1634 return False
1635
1636 num_frames = 3
1637 bound = '12345'
1638 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001639 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001640 'multipart/x-mixed-replace;boundary=' + bound)
1641 self.end_headers()
1642
1643 for i in xrange(num_frames):
1644 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001645 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001646 time.sleep(0.25)
1647 if i == 2:
1648 self.wfile.write('<title>PASS</title>')
1649 else:
1650 self.wfile.write('<title>page ' + str(i) + '</title>')
1651 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1652
1653 self.wfile.write('--' + bound + '--')
1654 return True
1655
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001656 def GetSSLSessionCacheHandler(self):
1657 """Send a reply containing a log of the session cache operations."""
1658
1659 if not self._ShouldHandleRequest('/ssl-session-cache'):
1660 return False
1661
1662 self.send_response(200)
1663 self.send_header('Content-Type', 'text/plain')
1664 self.end_headers()
1665 try:
1666 for (action, sessionID) in self.server.session_cache.log:
1667 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001668 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001669 self.wfile.write('Pass --https-record-resume in order to use' +
1670 ' this request')
1671 return True
1672
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001673 def CloseSocketHandler(self):
1674 """Closes the socket without sending anything."""
1675
1676 if not self._ShouldHandleRequest('/close-socket'):
1677 return False
1678
1679 self.wfile.close()
1680 return True
1681
initial.commit94958cf2008-07-26 22:42:52 +00001682 def DefaultResponseHandler(self):
1683 """This is the catch-all response handler for requests that aren't handled
1684 by one of the special handlers above.
1685 Note that we specify the content-length as without it the https connection
1686 is not closed properly (and the browser keeps expecting data)."""
1687
1688 contents = "Default response given for path: " + self.path
1689 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001690 self.send_header('Content-Type', 'text/html')
1691 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001692 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001693 if (self.command != 'HEAD'):
1694 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001695 return True
1696
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001697 def RedirectConnectHandler(self):
1698 """Sends a redirect to the CONNECT request for www.redirect.com. This
1699 response is not specified by the RFC, so the browser should not follow
1700 the redirect."""
1701
1702 if (self.path.find("www.redirect.com") < 0):
1703 return False
1704
1705 dest = "http://www.destination.com/foo.js"
1706
1707 self.send_response(302) # moved temporarily
1708 self.send_header('Location', dest)
1709 self.send_header('Connection', 'close')
1710 self.end_headers()
1711 return True
1712
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001713 def ServerAuthConnectHandler(self):
1714 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1715 response doesn't make sense because the proxy server cannot request
1716 server authentication."""
1717
1718 if (self.path.find("www.server-auth.com") < 0):
1719 return False
1720
1721 challenge = 'Basic realm="WallyWorld"'
1722
1723 self.send_response(401) # unauthorized
1724 self.send_header('WWW-Authenticate', challenge)
1725 self.send_header('Connection', 'close')
1726 self.end_headers()
1727 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001728
1729 def DefaultConnectResponseHandler(self):
1730 """This is the catch-all response handler for CONNECT requests that aren't
1731 handled by one of the special handlers above. Real Web servers respond
1732 with 400 to CONNECT requests."""
1733
1734 contents = "Your client has issued a malformed or illegal request."
1735 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001736 self.send_header('Content-Type', 'text/html')
1737 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001738 self.end_headers()
1739 self.wfile.write(contents)
1740 return True
1741
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001742 def DeviceManagementHandler(self):
1743 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001744
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001745 if not self._ShouldHandleRequest("/device_management"):
1746 return False
1747
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001748 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001749
1750 if not self.server._device_management_handler:
1751 import device_management
1752 policy_path = os.path.join(self.server.data_dir, 'device_management')
1753 self.server._device_management_handler = (
joaodasilva@chromium.org3c069da2012-11-20 16:17:15 +00001754 device_management.TestServer(policy_path, self.server.policy_keys))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001755
1756 http_response, raw_reply = (
1757 self.server._device_management_handler.HandleRequest(self.path,
1758 self.headers,
1759 raw_request))
1760 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001761 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001762 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001763 self.end_headers()
1764 self.wfile.write(raw_reply)
1765 return True
1766
initial.commit94958cf2008-07-26 22:42:52 +00001767 # called by the redirect handling function when there is no parameter
1768 def sendRedirectHelp(self, redirect_name):
1769 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001770 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001771 self.end_headers()
1772 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1773 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1774 self.wfile.write('</body></html>')
1775
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001776 # called by chunked handling function
1777 def sendChunkHelp(self, chunk):
1778 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1779 self.wfile.write('%X\r\n' % len(chunk))
1780 self.wfile.write(chunk)
1781 self.wfile.write('\r\n')
1782
akalin@chromium.org154bb132010-11-12 02:20:27 +00001783
1784class SyncPageHandler(BasePageHandler):
1785 """Handler for the main HTTP sync server."""
1786
1787 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001788 get_handlers = [self.ChromiumSyncTimeHandler,
1789 self.ChromiumSyncMigrationOpHandler,
1790 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001791 self.ChromiumSyncDisableNotificationsOpHandler,
1792 self.ChromiumSyncEnableNotificationsOpHandler,
1793 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001794 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001795 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001796 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001797 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001798 self.ChromiumSyncCreateSyncedBookmarksOpHandler,
1799 self.ChromiumSyncEnableKeystoreEncryptionOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001800
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001801 post_handlers = [self.ChromiumSyncCommandHandler,
1802 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001803 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001804 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001805 post_handlers, [])
1806
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001807
akalin@chromium.org154bb132010-11-12 02:20:27 +00001808 def ChromiumSyncTimeHandler(self):
1809 """Handle Chromium sync .../time requests.
1810
1811 The syncer sometimes checks server reachability by examining /time.
1812 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001813
akalin@chromium.org154bb132010-11-12 02:20:27 +00001814 test_name = "/chromiumsync/time"
1815 if not self._ShouldHandleRequest(test_name):
1816 return False
1817
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001818 # Chrome hates it if we send a response before reading the request.
1819 if self.headers.getheader('content-length'):
1820 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001821 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001822
akalin@chromium.org154bb132010-11-12 02:20:27 +00001823 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001824 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001825 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001826 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001827 return True
1828
1829 def ChromiumSyncCommandHandler(self):
1830 """Handle a chromiumsync command arriving via http.
1831
1832 This covers all sync protocol commands: authentication, getupdates, and
1833 commit.
1834 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001835
akalin@chromium.org154bb132010-11-12 02:20:27 +00001836 test_name = "/chromiumsync/command"
1837 if not self._ShouldHandleRequest(test_name):
1838 return False
1839
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001840 length = int(self.headers.getheader('content-length'))
1841 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001842 http_response = 200
1843 raw_reply = None
1844 if not self.server.GetAuthenticated():
1845 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001846 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1847 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001848 else:
1849 http_response, raw_reply = self.server.HandleCommand(
1850 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001851
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001852 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001853 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001854 if http_response == 401:
1855 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001856 self.end_headers()
1857 self.wfile.write(raw_reply)
1858 return True
1859
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001860 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001861 test_name = "/chromiumsync/migrate"
1862 if not self._ShouldHandleRequest(test_name):
1863 return False
1864
1865 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1866 self.path)
1867 self.send_response(http_response)
1868 self.send_header('Content-Type', 'text/html')
1869 self.send_header('Content-Length', len(raw_reply))
1870 self.end_headers()
1871 self.wfile.write(raw_reply)
1872 return True
1873
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001874 def ChromiumSyncCredHandler(self):
1875 test_name = "/chromiumsync/cred"
1876 if not self._ShouldHandleRequest(test_name):
1877 return False
1878 try:
1879 query = urlparse.urlparse(self.path)[4]
1880 cred_valid = urlparse.parse_qs(query)['valid']
1881 if cred_valid[0] == 'True':
1882 self.server.SetAuthenticated(True)
1883 else:
1884 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001885 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001886 self.server.SetAuthenticated(False)
1887
1888 http_response = 200
1889 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1890 self.send_response(http_response)
1891 self.send_header('Content-Type', 'text/html')
1892 self.send_header('Content-Length', len(raw_reply))
1893 self.end_headers()
1894 self.wfile.write(raw_reply)
1895 return True
1896
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001897 def ChromiumSyncDisableNotificationsOpHandler(self):
1898 test_name = "/chromiumsync/disablenotifications"
1899 if not self._ShouldHandleRequest(test_name):
1900 return False
1901 self.server.GetXmppServer().DisableNotifications()
1902 result = 200
1903 raw_reply = ('<html><title>Notifications disabled</title>'
1904 '<H1>Notifications disabled</H1></html>')
1905 self.send_response(result)
1906 self.send_header('Content-Type', 'text/html')
1907 self.send_header('Content-Length', len(raw_reply))
1908 self.end_headers()
1909 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001910 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001911
1912 def ChromiumSyncEnableNotificationsOpHandler(self):
1913 test_name = "/chromiumsync/enablenotifications"
1914 if not self._ShouldHandleRequest(test_name):
1915 return False
1916 self.server.GetXmppServer().EnableNotifications()
1917 result = 200
1918 raw_reply = ('<html><title>Notifications enabled</title>'
1919 '<H1>Notifications enabled</H1></html>')
1920 self.send_response(result)
1921 self.send_header('Content-Type', 'text/html')
1922 self.send_header('Content-Length', len(raw_reply))
1923 self.end_headers()
1924 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001925 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001926
1927 def ChromiumSyncSendNotificationOpHandler(self):
1928 test_name = "/chromiumsync/sendnotification"
1929 if not self._ShouldHandleRequest(test_name):
1930 return False
1931 query = urlparse.urlparse(self.path)[4]
1932 query_params = urlparse.parse_qs(query)
1933 channel = ''
1934 data = ''
1935 if 'channel' in query_params:
1936 channel = query_params['channel'][0]
1937 if 'data' in query_params:
1938 data = query_params['data'][0]
1939 self.server.GetXmppServer().SendNotification(channel, data)
1940 result = 200
1941 raw_reply = ('<html><title>Notification sent</title>'
1942 '<H1>Notification sent with channel "%s" '
1943 'and data "%s"</H1></html>'
1944 % (channel, data))
1945 self.send_response(result)
1946 self.send_header('Content-Type', 'text/html')
1947 self.send_header('Content-Length', len(raw_reply))
1948 self.end_headers()
1949 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001950 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001951
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001952 def ChromiumSyncBirthdayErrorOpHandler(self):
1953 test_name = "/chromiumsync/birthdayerror"
1954 if not self._ShouldHandleRequest(test_name):
1955 return False
1956 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1957 self.send_response(result)
1958 self.send_header('Content-Type', 'text/html')
1959 self.send_header('Content-Length', len(raw_reply))
1960 self.end_headers()
1961 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001962 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001963
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001964 def ChromiumSyncTransientErrorOpHandler(self):
1965 test_name = "/chromiumsync/transienterror"
1966 if not self._ShouldHandleRequest(test_name):
1967 return False
1968 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1969 self.send_response(result)
1970 self.send_header('Content-Type', 'text/html')
1971 self.send_header('Content-Length', len(raw_reply))
1972 self.end_headers()
1973 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001974 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001975
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001976 def ChromiumSyncErrorOpHandler(self):
1977 test_name = "/chromiumsync/error"
1978 if not self._ShouldHandleRequest(test_name):
1979 return False
1980 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1981 self.path)
1982 self.send_response(result)
1983 self.send_header('Content-Type', 'text/html')
1984 self.send_header('Content-Length', len(raw_reply))
1985 self.end_headers()
1986 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001987 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001988
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001989 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1990 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001991 if not self._ShouldHandleRequest(test_name):
1992 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001993 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001994 self.send_response(result)
1995 self.send_header('Content-Type', 'text/html')
1996 self.send_header('Content-Length', len(raw_reply))
1997 self.end_headers()
1998 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001999 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00002000
zea@chromium.orga606cbb2012-03-16 04:24:18 +00002001 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
2002 test_name = "/chromiumsync/createsyncedbookmarks"
2003 if not self._ShouldHandleRequest(test_name):
2004 return False
2005 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
2006 self.send_response(result)
2007 self.send_header('Content-Type', 'text/html')
2008 self.send_header('Content-Length', len(raw_reply))
2009 self.end_headers()
2010 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002011 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00002012
zea@chromium.orgbb26c702012-11-15 02:43:40 +00002013 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
2014 test_name = "/chromiumsync/enablekeystoreencryption"
2015 if not self._ShouldHandleRequest(test_name):
2016 return False
2017 result, raw_reply = (
2018 self.server._sync_handler.HandleEnableKeystoreEncryption())
2019 self.send_response(result)
2020 self.send_header('Content-Type', 'text/html')
2021 self.send_header('Content-Length', len(raw_reply))
2022 self.end_headers()
2023 self.wfile.write(raw_reply)
2024 return True
2025
akalin@chromium.org154bb132010-11-12 02:20:27 +00002026
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002027class OCSPHandler(BasePageHandler):
2028 def __init__(self, request, client_address, socket_server):
2029 handlers = [self.OCSPResponse]
2030 self.ocsp_response = socket_server.ocsp_response
2031 BasePageHandler.__init__(self, request, client_address, socket_server,
2032 [], handlers, [], handlers, [])
2033
2034 def OCSPResponse(self):
2035 self.send_response(200)
2036 self.send_header('Content-Type', 'application/ocsp-response')
2037 self.send_header('Content-Length', str(len(self.ocsp_response)))
2038 self.end_headers()
2039
2040 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002041
mattm@chromium.org830a3712012-11-07 23:00:07 +00002042
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002043class TCPEchoHandler(SocketServer.BaseRequestHandler):
2044 """The RequestHandler class for TCP echo server.
2045
2046 It is instantiated once per connection to the server, and overrides the
2047 handle() method to implement communication to the client.
2048 """
2049
2050 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002051 """Handles the request from the client and constructs a response."""
2052
2053 data = self.request.recv(65536).strip()
2054 # Verify the "echo request" message received from the client. Send back
2055 # "echo response" message if "echo request" message is valid.
2056 try:
2057 return_data = echo_message.GetEchoResponseData(data)
2058 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002059 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002060 except ValueError:
2061 return
2062
2063 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002064
2065
2066class UDPEchoHandler(SocketServer.BaseRequestHandler):
2067 """The RequestHandler class for UDP echo server.
2068
2069 It is instantiated once per connection to the server, and overrides the
2070 handle() method to implement communication to the client.
2071 """
2072
2073 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002074 """Handles the request from the client and constructs a response."""
2075
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002076 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002077 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002078 # Verify the "echo request" message received from the client. Send back
2079 # "echo response" message if "echo request" message is valid.
2080 try:
2081 return_data = echo_message.GetEchoResponseData(data)
2082 if not return_data:
2083 return
2084 except ValueError:
2085 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002086 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002087
2088
bashi@chromium.org33233532012-09-08 17:37:24 +00002089class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2090 """A request handler that behaves as a proxy server which requires
2091 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2092 """
2093
2094 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2095
2096 def parse_request(self):
2097 """Overrides parse_request to check credential."""
2098
2099 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2100 return False
2101
2102 auth = self.headers.getheader('Proxy-Authorization')
2103 if auth != self._AUTH_CREDENTIAL:
2104 self.send_response(407)
2105 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2106 self.end_headers()
2107 return False
2108
2109 return True
2110
2111 def _start_read_write(self, sock):
2112 sock.setblocking(0)
2113 self.request.setblocking(0)
2114 rlist = [self.request, sock]
2115 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002116 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002117 if errors:
2118 self.send_response(500)
2119 self.end_headers()
2120 return
2121 for s in ready_sockets:
2122 received = s.recv(1024)
2123 if len(received) == 0:
2124 return
2125 if s == self.request:
2126 other = sock
2127 else:
2128 other = self.request
2129 other.send(received)
2130
2131 def _do_common_method(self):
2132 url = urlparse.urlparse(self.path)
2133 port = url.port
2134 if not port:
2135 if url.scheme == 'http':
2136 port = 80
2137 elif url.scheme == 'https':
2138 port = 443
2139 if not url.hostname or not port:
2140 self.send_response(400)
2141 self.end_headers()
2142 return
2143
2144 if len(url.path) == 0:
2145 path = '/'
2146 else:
2147 path = url.path
2148 if len(url.query) > 0:
2149 path = '%s?%s' % (url.path, url.query)
2150
2151 sock = None
2152 try:
2153 sock = socket.create_connection((url.hostname, port))
2154 sock.send('%s %s %s\r\n' % (
2155 self.command, path, self.protocol_version))
2156 for header in self.headers.headers:
2157 header = header.strip()
2158 if (header.lower().startswith('connection') or
2159 header.lower().startswith('proxy')):
2160 continue
2161 sock.send('%s\r\n' % header)
2162 sock.send('\r\n')
2163 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002164 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002165 self.send_response(500)
2166 self.end_headers()
2167 finally:
2168 if sock is not None:
2169 sock.close()
2170
2171 def do_CONNECT(self):
2172 try:
2173 pos = self.path.rfind(':')
2174 host = self.path[:pos]
2175 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002176 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002177 self.send_response(400)
2178 self.end_headers()
2179
2180 try:
2181 sock = socket.create_connection((host, port))
2182 self.send_response(200, 'Connection established')
2183 self.end_headers()
2184 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002185 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002186 self.send_response(500)
2187 self.end_headers()
2188 finally:
2189 sock.close()
2190
2191 def do_GET(self):
2192 self._do_common_method()
2193
2194 def do_HEAD(self):
2195 self._do_common_method()
2196
2197
mattm@chromium.org830a3712012-11-07 23:00:07 +00002198class ServerRunner(testserver_base.TestServerRunner):
2199 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002200
mattm@chromium.org830a3712012-11-07 23:00:07 +00002201 def __init__(self):
2202 super(ServerRunner, self).__init__()
2203 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002204
mattm@chromium.org830a3712012-11-07 23:00:07 +00002205 def __make_data_dir(self):
2206 if self.options.data_dir:
2207 if not os.path.isdir(self.options.data_dir):
2208 raise testserver_base.OptionError('specified data dir not found: ' +
2209 self.options.data_dir + ' exiting...')
2210 my_data_dir = self.options.data_dir
2211 else:
2212 # Create the default path to our data dir, relative to the exe dir.
2213 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2214 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002215
mattm@chromium.org830a3712012-11-07 23:00:07 +00002216 #TODO(ibrar): Must use Find* funtion defined in google\tools
2217 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002218
mattm@chromium.org830a3712012-11-07 23:00:07 +00002219 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002220
mattm@chromium.org830a3712012-11-07 23:00:07 +00002221 def create_server(self, server_data):
2222 port = self.options.port
2223 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002224
mattm@chromium.org830a3712012-11-07 23:00:07 +00002225 if self.options.server_type == SERVER_HTTP:
2226 if self.options.https:
2227 pem_cert_and_key = None
2228 if self.options.cert_and_key_file:
2229 if not os.path.isfile(self.options.cert_and_key_file):
2230 raise testserver_base.OptionError(
2231 'specified server cert file not found: ' +
2232 self.options.cert_and_key_file + ' exiting...')
2233 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002234 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002235 # generate a new certificate and run an OCSP server for it.
2236 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2237 print ('OCSP server started on %s:%d...' %
2238 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002239
mattm@chromium.org830a3712012-11-07 23:00:07 +00002240 ocsp_der = None
2241 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002242
mattm@chromium.org830a3712012-11-07 23:00:07 +00002243 if self.options.ocsp == 'ok':
2244 ocsp_state = minica.OCSP_STATE_GOOD
2245 elif self.options.ocsp == 'revoked':
2246 ocsp_state = minica.OCSP_STATE_REVOKED
2247 elif self.options.ocsp == 'invalid':
2248 ocsp_state = minica.OCSP_STATE_INVALID
2249 elif self.options.ocsp == 'unauthorized':
2250 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2251 elif self.options.ocsp == 'unknown':
2252 ocsp_state = minica.OCSP_STATE_UNKNOWN
2253 else:
2254 raise testserver_base.OptionError('unknown OCSP status: ' +
2255 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002256
mattm@chromium.org830a3712012-11-07 23:00:07 +00002257 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2258 subject = "127.0.0.1",
2259 ocsp_url = ("http://%s:%d/ocsp" %
2260 (host, self.__ocsp_server.server_port)),
2261 ocsp_state = ocsp_state)
2262
2263 self.__ocsp_server.ocsp_response = ocsp_der
2264
2265 for ca_cert in self.options.ssl_client_ca:
2266 if not os.path.isfile(ca_cert):
2267 raise testserver_base.OptionError(
2268 'specified trusted client CA file not found: ' + ca_cert +
2269 ' exiting...')
2270 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2271 self.options.ssl_client_auth,
2272 self.options.ssl_client_ca,
2273 self.options.ssl_bulk_cipher,
2274 self.options.record_resume,
2275 self.options.tls_intolerant)
2276 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2277 else:
2278 server = HTTPServer((host, port), TestPageHandler)
2279 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2280
2281 server.data_dir = self.__make_data_dir()
2282 server.file_root_url = self.options.file_root_url
2283 server_data['port'] = server.server_port
2284 server._device_management_handler = None
2285 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00002286 server.gdata_auth_token = self.options.auth_token
2287 elif self.options.server_type == SERVER_WEBSOCKET:
2288 # Launch pywebsocket via WebSocketServer.
2289 logger = logging.getLogger()
2290 logger.addHandler(logging.StreamHandler())
2291 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2292 # is required to work correctly. It should be fixed from pywebsocket side.
2293 os.chdir(self.__make_data_dir())
2294 websocket_options = WebSocketOptions(host, port, '.')
2295 if self.options.cert_and_key_file:
2296 websocket_options.use_tls = True
2297 websocket_options.private_key = self.options.cert_and_key_file
2298 websocket_options.certificate = self.options.cert_and_key_file
2299 if self.options.ssl_client_auth:
2300 websocket_options.tls_client_auth = True
2301 if len(self.options.ssl_client_ca) != 1:
2302 raise testserver_base.OptionError(
2303 'one trusted client CA file should be specified')
2304 if not os.path.isfile(self.options.ssl_client_ca[0]):
2305 raise testserver_base.OptionError(
2306 'specified trusted client CA file not found: ' +
2307 self.options.ssl_client_ca[0] + ' exiting...')
2308 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2309 server = WebSocketServer(websocket_options)
2310 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2311 server_data['port'] = server.server_port
2312 elif self.options.server_type == SERVER_SYNC:
2313 xmpp_port = self.options.xmpp_port
2314 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2315 print 'Sync HTTP server started on port %d...' % server.server_port
2316 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2317 server_data['port'] = server.server_port
2318 server_data['xmpp_port'] = server.xmpp_port
2319 elif self.options.server_type == SERVER_TCP_ECHO:
2320 # Used for generating the key (randomly) that encodes the "echo request"
2321 # message.
2322 random.seed()
2323 server = TCPEchoServer((host, port), TCPEchoHandler)
2324 print 'Echo TCP server started on port %d...' % server.server_port
2325 server_data['port'] = server.server_port
2326 elif self.options.server_type == SERVER_UDP_ECHO:
2327 # Used for generating the key (randomly) that encodes the "echo request"
2328 # message.
2329 random.seed()
2330 server = UDPEchoServer((host, port), UDPEchoHandler)
2331 print 'Echo UDP server started on port %d...' % server.server_port
2332 server_data['port'] = server.server_port
2333 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2334 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2335 print 'BasicAuthProxy server started on port %d...' % server.server_port
2336 server_data['port'] = server.server_port
2337 elif self.options.server_type == SERVER_FTP:
2338 my_data_dir = self.__make_data_dir()
2339
2340 # Instantiate a dummy authorizer for managing 'virtual' users
2341 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2342
2343 # Define a new user having full r/w permissions and a read-only
2344 # anonymous user
2345 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2346
2347 authorizer.add_anonymous(my_data_dir)
2348
2349 # Instantiate FTP handler class
2350 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2351 ftp_handler.authorizer = authorizer
2352
2353 # Define a customized banner (string returned when client connects)
2354 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2355 pyftpdlib.ftpserver.__ver__)
2356
2357 # Instantiate FTP server class and listen to address:port
2358 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2359 server_data['port'] = server.socket.getsockname()[1]
2360 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002361 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002362 raise testserver_base.OptionError('unknown server type' +
2363 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002364
mattm@chromium.org830a3712012-11-07 23:00:07 +00002365 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002366
mattm@chromium.org830a3712012-11-07 23:00:07 +00002367 def run_server(self):
2368 if self.__ocsp_server:
2369 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002370
mattm@chromium.org830a3712012-11-07 23:00:07 +00002371 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002372
mattm@chromium.org830a3712012-11-07 23:00:07 +00002373 if self.__ocsp_server:
2374 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002375
mattm@chromium.org830a3712012-11-07 23:00:07 +00002376 def add_options(self):
2377 testserver_base.TestServerRunner.add_options(self)
2378 self.option_parser.add_option('-f', '--ftp', action='store_const',
2379 const=SERVER_FTP, default=SERVER_HTTP,
2380 dest='server_type',
2381 help='start up an FTP server.')
2382 self.option_parser.add_option('--sync', action='store_const',
2383 const=SERVER_SYNC, default=SERVER_HTTP,
2384 dest='server_type',
2385 help='start up a sync server.')
2386 self.option_parser.add_option('--tcp-echo', action='store_const',
2387 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2388 dest='server_type',
2389 help='start up a tcp echo server.')
2390 self.option_parser.add_option('--udp-echo', action='store_const',
2391 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2392 dest='server_type',
2393 help='start up a udp echo server.')
2394 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2395 const=SERVER_BASIC_AUTH_PROXY,
2396 default=SERVER_HTTP, dest='server_type',
2397 help='start up a proxy server which requires '
2398 'basic authentication.')
2399 self.option_parser.add_option('--websocket', action='store_const',
2400 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2401 dest='server_type',
2402 help='start up a WebSocket server.')
2403 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2404 help='Port used by the XMPP server. If '
2405 'unspecified, the XMPP server will listen on '
2406 'an ephemeral port.')
2407 self.option_parser.add_option('--data-dir', dest='data_dir',
2408 help='Directory from which to read the '
2409 'files.')
2410 self.option_parser.add_option('--https', action='store_true',
2411 dest='https', help='Specify that https '
2412 'should be used.')
2413 self.option_parser.add_option('--cert-and-key-file',
2414 dest='cert_and_key_file', help='specify the '
2415 'path to the file containing the certificate '
2416 'and private key for the server in PEM '
2417 'format')
2418 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2419 help='The type of OCSP response generated '
2420 'for the automatically generated '
2421 'certificate. One of [ok,revoked,invalid]')
2422 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2423 default='0', type='int',
2424 help='If nonzero, certain TLS connections '
2425 'will be aborted in order to test version '
2426 'fallback. 1 means all TLS versions will be '
2427 'aborted. 2 means TLS 1.1 or higher will be '
2428 'aborted. 3 means TLS 1.2 or higher will be '
2429 'aborted.')
2430 self.option_parser.add_option('--https-record-resume',
2431 dest='record_resume', const=True,
2432 default=False, action='store_const',
2433 help='Record resumption cache events rather '
2434 'than resuming as normal. Allows the use of '
2435 'the /ssl-session-cache request')
2436 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2437 help='Require SSL client auth on every '
2438 'connection.')
2439 self.option_parser.add_option('--ssl-client-ca', action='append',
2440 default=[], help='Specify that the client '
2441 'certificate request should include the CA '
2442 'named in the subject of the DER-encoded '
2443 'certificate contained in the specified '
2444 'file. This option may appear multiple '
2445 'times, indicating multiple CA names should '
2446 'be sent in the request.')
2447 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2448 help='Specify the bulk encryption '
2449 'algorithm(s) that will be accepted by the '
2450 'SSL server. Valid values are "aes256", '
2451 '"aes128", "3des", "rc4". If omitted, all '
2452 'algorithms will be used. This option may '
2453 'appear multiple times, indicating '
2454 'multiple algorithms should be enabled.');
2455 self.option_parser.add_option('--file-root-url', default='/files/',
2456 help='Specify a root URL for files served.')
2457 self.option_parser.add_option('--policy-key', action='append',
2458 dest='policy_keys',
2459 help='Specify a path to a PEM-encoded '
2460 'private key to use for policy signing. May '
2461 'be specified multiple times in order to '
2462 'load multipe keys into the server. If the '
2463 'server has multiple keys, it will rotate '
2464 'through them in at each request a '
2465 'round-robin fashion. The server will '
2466 'generate a random key if none is specified '
2467 'on the command line.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002468 self.option_parser.add_option('--auth-token', dest='auth_token',
2469 help='Specify the auth token which should be '
2470 'used in the authorization header for GData.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002471
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002472
initial.commit94958cf2008-07-26 22:42:52 +00002473if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002474 sys.exit(ServerRunner().main())