blob: eefc448b2bd54bac6817c5eebda6d4bb7fbdee2d [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.org4a6763a2012-12-05 05:46:28 +00001791 self.ChromiumSyncXmppCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001792 self.ChromiumSyncDisableNotificationsOpHandler,
1793 self.ChromiumSyncEnableNotificationsOpHandler,
1794 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001795 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001796 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001797 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001798 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001799 self.ChromiumSyncCreateSyncedBookmarksOpHandler,
1800 self.ChromiumSyncEnableKeystoreEncryptionOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001801
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001802 post_handlers = [self.ChromiumSyncCommandHandler,
1803 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001804 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001805 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001806 post_handlers, [])
1807
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001808
akalin@chromium.org154bb132010-11-12 02:20:27 +00001809 def ChromiumSyncTimeHandler(self):
1810 """Handle Chromium sync .../time requests.
1811
1812 The syncer sometimes checks server reachability by examining /time.
1813 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001814
akalin@chromium.org154bb132010-11-12 02:20:27 +00001815 test_name = "/chromiumsync/time"
1816 if not self._ShouldHandleRequest(test_name):
1817 return False
1818
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001819 # Chrome hates it if we send a response before reading the request.
1820 if self.headers.getheader('content-length'):
1821 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001822 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001823
akalin@chromium.org154bb132010-11-12 02:20:27 +00001824 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001825 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001826 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001827 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001828 return True
1829
1830 def ChromiumSyncCommandHandler(self):
1831 """Handle a chromiumsync command arriving via http.
1832
1833 This covers all sync protocol commands: authentication, getupdates, and
1834 commit.
1835 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001836
akalin@chromium.org154bb132010-11-12 02:20:27 +00001837 test_name = "/chromiumsync/command"
1838 if not self._ShouldHandleRequest(test_name):
1839 return False
1840
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001841 length = int(self.headers.getheader('content-length'))
1842 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001843 http_response = 200
1844 raw_reply = None
1845 if not self.server.GetAuthenticated():
1846 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001847 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1848 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001849 else:
1850 http_response, raw_reply = self.server.HandleCommand(
1851 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001852
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001853 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001854 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001855 if http_response == 401:
1856 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001857 self.end_headers()
1858 self.wfile.write(raw_reply)
1859 return True
1860
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001861 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001862 test_name = "/chromiumsync/migrate"
1863 if not self._ShouldHandleRequest(test_name):
1864 return False
1865
1866 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1867 self.path)
1868 self.send_response(http_response)
1869 self.send_header('Content-Type', 'text/html')
1870 self.send_header('Content-Length', len(raw_reply))
1871 self.end_headers()
1872 self.wfile.write(raw_reply)
1873 return True
1874
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001875 def ChromiumSyncCredHandler(self):
1876 test_name = "/chromiumsync/cred"
1877 if not self._ShouldHandleRequest(test_name):
1878 return False
1879 try:
1880 query = urlparse.urlparse(self.path)[4]
1881 cred_valid = urlparse.parse_qs(query)['valid']
1882 if cred_valid[0] == 'True':
1883 self.server.SetAuthenticated(True)
1884 else:
1885 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001886 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001887 self.server.SetAuthenticated(False)
1888
1889 http_response = 200
1890 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1891 self.send_response(http_response)
1892 self.send_header('Content-Type', 'text/html')
1893 self.send_header('Content-Length', len(raw_reply))
1894 self.end_headers()
1895 self.wfile.write(raw_reply)
1896 return True
1897
akalin@chromium.org4a6763a2012-12-05 05:46:28 +00001898 def ChromiumSyncXmppCredHandler(self):
1899 test_name = "/chromiumsync/xmppcred"
1900 if not self._ShouldHandleRequest(test_name):
1901 return False
1902 xmpp_server = self.server.GetXmppServer()
1903 try:
1904 query = urlparse.urlparse(self.path)[4]
1905 cred_valid = urlparse.parse_qs(query)['valid']
1906 if cred_valid[0] == 'True':
1907 xmpp_server.SetAuthenticated(True)
1908 else:
1909 xmpp_server.SetAuthenticated(False)
1910 except:
1911 xmpp_server.SetAuthenticated(False)
1912
1913 http_response = 200
1914 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated()
1915 self.send_response(http_response)
1916 self.send_header('Content-Type', 'text/html')
1917 self.send_header('Content-Length', len(raw_reply))
1918 self.end_headers()
1919 self.wfile.write(raw_reply)
1920 return True
1921
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001922 def ChromiumSyncDisableNotificationsOpHandler(self):
1923 test_name = "/chromiumsync/disablenotifications"
1924 if not self._ShouldHandleRequest(test_name):
1925 return False
1926 self.server.GetXmppServer().DisableNotifications()
1927 result = 200
1928 raw_reply = ('<html><title>Notifications disabled</title>'
1929 '<H1>Notifications disabled</H1></html>')
1930 self.send_response(result)
1931 self.send_header('Content-Type', 'text/html')
1932 self.send_header('Content-Length', len(raw_reply))
1933 self.end_headers()
1934 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001935 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001936
1937 def ChromiumSyncEnableNotificationsOpHandler(self):
1938 test_name = "/chromiumsync/enablenotifications"
1939 if not self._ShouldHandleRequest(test_name):
1940 return False
1941 self.server.GetXmppServer().EnableNotifications()
1942 result = 200
1943 raw_reply = ('<html><title>Notifications enabled</title>'
1944 '<H1>Notifications enabled</H1></html>')
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
1952 def ChromiumSyncSendNotificationOpHandler(self):
1953 test_name = "/chromiumsync/sendnotification"
1954 if not self._ShouldHandleRequest(test_name):
1955 return False
1956 query = urlparse.urlparse(self.path)[4]
1957 query_params = urlparse.parse_qs(query)
1958 channel = ''
1959 data = ''
1960 if 'channel' in query_params:
1961 channel = query_params['channel'][0]
1962 if 'data' in query_params:
1963 data = query_params['data'][0]
1964 self.server.GetXmppServer().SendNotification(channel, data)
1965 result = 200
1966 raw_reply = ('<html><title>Notification sent</title>'
1967 '<H1>Notification sent with channel "%s" '
1968 'and data "%s"</H1></html>'
1969 % (channel, data))
1970 self.send_response(result)
1971 self.send_header('Content-Type', 'text/html')
1972 self.send_header('Content-Length', len(raw_reply))
1973 self.end_headers()
1974 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001975 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001976
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001977 def ChromiumSyncBirthdayErrorOpHandler(self):
1978 test_name = "/chromiumsync/birthdayerror"
1979 if not self._ShouldHandleRequest(test_name):
1980 return False
1981 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
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.orgb56c12b2011-07-30 02:17:37 +00001988
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001989 def ChromiumSyncTransientErrorOpHandler(self):
1990 test_name = "/chromiumsync/transienterror"
1991 if not self._ShouldHandleRequest(test_name):
1992 return False
1993 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1994 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
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00002000
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00002001 def ChromiumSyncErrorOpHandler(self):
2002 test_name = "/chromiumsync/error"
2003 if not self._ShouldHandleRequest(test_name):
2004 return False
2005 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
2006 self.path)
2007 self.send_response(result)
2008 self.send_header('Content-Type', 'text/html')
2009 self.send_header('Content-Length', len(raw_reply))
2010 self.end_headers()
2011 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002012 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00002013
nyquist@chromium.org154389d2012-09-07 20:33:58 +00002014 def ChromiumSyncSyncTabFaviconsOpHandler(self):
2015 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00002016 if not self._ShouldHandleRequest(test_name):
2017 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00002018 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00002019 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)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002024 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00002025
zea@chromium.orga606cbb2012-03-16 04:24:18 +00002026 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
2027 test_name = "/chromiumsync/createsyncedbookmarks"
2028 if not self._ShouldHandleRequest(test_name):
2029 return False
2030 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
2031 self.send_response(result)
2032 self.send_header('Content-Type', 'text/html')
2033 self.send_header('Content-Length', len(raw_reply))
2034 self.end_headers()
2035 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002036 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00002037
zea@chromium.orgbb26c702012-11-15 02:43:40 +00002038 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
2039 test_name = "/chromiumsync/enablekeystoreencryption"
2040 if not self._ShouldHandleRequest(test_name):
2041 return False
2042 result, raw_reply = (
2043 self.server._sync_handler.HandleEnableKeystoreEncryption())
2044 self.send_response(result)
2045 self.send_header('Content-Type', 'text/html')
2046 self.send_header('Content-Length', len(raw_reply))
2047 self.end_headers()
2048 self.wfile.write(raw_reply)
2049 return True
2050
akalin@chromium.org154bb132010-11-12 02:20:27 +00002051
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002052class OCSPHandler(BasePageHandler):
2053 def __init__(self, request, client_address, socket_server):
2054 handlers = [self.OCSPResponse]
2055 self.ocsp_response = socket_server.ocsp_response
2056 BasePageHandler.__init__(self, request, client_address, socket_server,
2057 [], handlers, [], handlers, [])
2058
2059 def OCSPResponse(self):
2060 self.send_response(200)
2061 self.send_header('Content-Type', 'application/ocsp-response')
2062 self.send_header('Content-Length', str(len(self.ocsp_response)))
2063 self.end_headers()
2064
2065 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002066
mattm@chromium.org830a3712012-11-07 23:00:07 +00002067
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002068class TCPEchoHandler(SocketServer.BaseRequestHandler):
2069 """The RequestHandler class for TCP echo server.
2070
2071 It is instantiated once per connection to the server, and overrides the
2072 handle() method to implement communication to the client.
2073 """
2074
2075 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002076 """Handles the request from the client and constructs a response."""
2077
2078 data = self.request.recv(65536).strip()
2079 # Verify the "echo request" message received from the client. Send back
2080 # "echo response" message if "echo request" message is valid.
2081 try:
2082 return_data = echo_message.GetEchoResponseData(data)
2083 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002084 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002085 except ValueError:
2086 return
2087
2088 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002089
2090
2091class UDPEchoHandler(SocketServer.BaseRequestHandler):
2092 """The RequestHandler class for UDP echo server.
2093
2094 It is instantiated once per connection to the server, and overrides the
2095 handle() method to implement communication to the client.
2096 """
2097
2098 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002099 """Handles the request from the client and constructs a response."""
2100
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002101 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002102 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002103 # Verify the "echo request" message received from the client. Send back
2104 # "echo response" message if "echo request" message is valid.
2105 try:
2106 return_data = echo_message.GetEchoResponseData(data)
2107 if not return_data:
2108 return
2109 except ValueError:
2110 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002111 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002112
2113
bashi@chromium.org33233532012-09-08 17:37:24 +00002114class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2115 """A request handler that behaves as a proxy server which requires
2116 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2117 """
2118
2119 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2120
2121 def parse_request(self):
2122 """Overrides parse_request to check credential."""
2123
2124 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2125 return False
2126
2127 auth = self.headers.getheader('Proxy-Authorization')
2128 if auth != self._AUTH_CREDENTIAL:
2129 self.send_response(407)
2130 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2131 self.end_headers()
2132 return False
2133
2134 return True
2135
2136 def _start_read_write(self, sock):
2137 sock.setblocking(0)
2138 self.request.setblocking(0)
2139 rlist = [self.request, sock]
2140 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002141 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002142 if errors:
2143 self.send_response(500)
2144 self.end_headers()
2145 return
2146 for s in ready_sockets:
2147 received = s.recv(1024)
2148 if len(received) == 0:
2149 return
2150 if s == self.request:
2151 other = sock
2152 else:
2153 other = self.request
2154 other.send(received)
2155
2156 def _do_common_method(self):
2157 url = urlparse.urlparse(self.path)
2158 port = url.port
2159 if not port:
2160 if url.scheme == 'http':
2161 port = 80
2162 elif url.scheme == 'https':
2163 port = 443
2164 if not url.hostname or not port:
2165 self.send_response(400)
2166 self.end_headers()
2167 return
2168
2169 if len(url.path) == 0:
2170 path = '/'
2171 else:
2172 path = url.path
2173 if len(url.query) > 0:
2174 path = '%s?%s' % (url.path, url.query)
2175
2176 sock = None
2177 try:
2178 sock = socket.create_connection((url.hostname, port))
2179 sock.send('%s %s %s\r\n' % (
2180 self.command, path, self.protocol_version))
2181 for header in self.headers.headers:
2182 header = header.strip()
2183 if (header.lower().startswith('connection') or
2184 header.lower().startswith('proxy')):
2185 continue
2186 sock.send('%s\r\n' % header)
2187 sock.send('\r\n')
2188 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002189 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002190 self.send_response(500)
2191 self.end_headers()
2192 finally:
2193 if sock is not None:
2194 sock.close()
2195
2196 def do_CONNECT(self):
2197 try:
2198 pos = self.path.rfind(':')
2199 host = self.path[:pos]
2200 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002201 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002202 self.send_response(400)
2203 self.end_headers()
2204
2205 try:
2206 sock = socket.create_connection((host, port))
2207 self.send_response(200, 'Connection established')
2208 self.end_headers()
2209 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002210 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002211 self.send_response(500)
2212 self.end_headers()
2213 finally:
2214 sock.close()
2215
2216 def do_GET(self):
2217 self._do_common_method()
2218
2219 def do_HEAD(self):
2220 self._do_common_method()
2221
2222
mattm@chromium.org830a3712012-11-07 23:00:07 +00002223class ServerRunner(testserver_base.TestServerRunner):
2224 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002225
mattm@chromium.org830a3712012-11-07 23:00:07 +00002226 def __init__(self):
2227 super(ServerRunner, self).__init__()
2228 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002229
mattm@chromium.org830a3712012-11-07 23:00:07 +00002230 def __make_data_dir(self):
2231 if self.options.data_dir:
2232 if not os.path.isdir(self.options.data_dir):
2233 raise testserver_base.OptionError('specified data dir not found: ' +
2234 self.options.data_dir + ' exiting...')
2235 my_data_dir = self.options.data_dir
2236 else:
2237 # Create the default path to our data dir, relative to the exe dir.
2238 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2239 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002240
mattm@chromium.org830a3712012-11-07 23:00:07 +00002241 #TODO(ibrar): Must use Find* funtion defined in google\tools
2242 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002243
mattm@chromium.org830a3712012-11-07 23:00:07 +00002244 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002245
mattm@chromium.org830a3712012-11-07 23:00:07 +00002246 def create_server(self, server_data):
2247 port = self.options.port
2248 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002249
mattm@chromium.org830a3712012-11-07 23:00:07 +00002250 if self.options.server_type == SERVER_HTTP:
2251 if self.options.https:
2252 pem_cert_and_key = None
2253 if self.options.cert_and_key_file:
2254 if not os.path.isfile(self.options.cert_and_key_file):
2255 raise testserver_base.OptionError(
2256 'specified server cert file not found: ' +
2257 self.options.cert_and_key_file + ' exiting...')
2258 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002259 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002260 # generate a new certificate and run an OCSP server for it.
2261 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2262 print ('OCSP server started on %s:%d...' %
2263 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002264
mattm@chromium.org830a3712012-11-07 23:00:07 +00002265 ocsp_der = None
2266 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002267
mattm@chromium.org830a3712012-11-07 23:00:07 +00002268 if self.options.ocsp == 'ok':
2269 ocsp_state = minica.OCSP_STATE_GOOD
2270 elif self.options.ocsp == 'revoked':
2271 ocsp_state = minica.OCSP_STATE_REVOKED
2272 elif self.options.ocsp == 'invalid':
2273 ocsp_state = minica.OCSP_STATE_INVALID
2274 elif self.options.ocsp == 'unauthorized':
2275 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2276 elif self.options.ocsp == 'unknown':
2277 ocsp_state = minica.OCSP_STATE_UNKNOWN
2278 else:
2279 raise testserver_base.OptionError('unknown OCSP status: ' +
2280 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002281
mattm@chromium.org830a3712012-11-07 23:00:07 +00002282 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2283 subject = "127.0.0.1",
2284 ocsp_url = ("http://%s:%d/ocsp" %
2285 (host, self.__ocsp_server.server_port)),
2286 ocsp_state = ocsp_state)
2287
2288 self.__ocsp_server.ocsp_response = ocsp_der
2289
2290 for ca_cert in self.options.ssl_client_ca:
2291 if not os.path.isfile(ca_cert):
2292 raise testserver_base.OptionError(
2293 'specified trusted client CA file not found: ' + ca_cert +
2294 ' exiting...')
2295 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2296 self.options.ssl_client_auth,
2297 self.options.ssl_client_ca,
2298 self.options.ssl_bulk_cipher,
2299 self.options.record_resume,
2300 self.options.tls_intolerant)
2301 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2302 else:
2303 server = HTTPServer((host, port), TestPageHandler)
2304 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2305
2306 server.data_dir = self.__make_data_dir()
2307 server.file_root_url = self.options.file_root_url
2308 server_data['port'] = server.server_port
2309 server._device_management_handler = None
2310 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00002311 server.gdata_auth_token = self.options.auth_token
2312 elif self.options.server_type == SERVER_WEBSOCKET:
2313 # Launch pywebsocket via WebSocketServer.
2314 logger = logging.getLogger()
2315 logger.addHandler(logging.StreamHandler())
2316 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2317 # is required to work correctly. It should be fixed from pywebsocket side.
2318 os.chdir(self.__make_data_dir())
2319 websocket_options = WebSocketOptions(host, port, '.')
2320 if self.options.cert_and_key_file:
2321 websocket_options.use_tls = True
2322 websocket_options.private_key = self.options.cert_and_key_file
2323 websocket_options.certificate = self.options.cert_and_key_file
2324 if self.options.ssl_client_auth:
2325 websocket_options.tls_client_auth = True
2326 if len(self.options.ssl_client_ca) != 1:
2327 raise testserver_base.OptionError(
2328 'one trusted client CA file should be specified')
2329 if not os.path.isfile(self.options.ssl_client_ca[0]):
2330 raise testserver_base.OptionError(
2331 'specified trusted client CA file not found: ' +
2332 self.options.ssl_client_ca[0] + ' exiting...')
2333 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2334 server = WebSocketServer(websocket_options)
2335 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2336 server_data['port'] = server.server_port
2337 elif self.options.server_type == SERVER_SYNC:
2338 xmpp_port = self.options.xmpp_port
2339 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2340 print 'Sync HTTP server started on port %d...' % server.server_port
2341 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2342 server_data['port'] = server.server_port
2343 server_data['xmpp_port'] = server.xmpp_port
2344 elif self.options.server_type == SERVER_TCP_ECHO:
2345 # Used for generating the key (randomly) that encodes the "echo request"
2346 # message.
2347 random.seed()
2348 server = TCPEchoServer((host, port), TCPEchoHandler)
2349 print 'Echo TCP server started on port %d...' % server.server_port
2350 server_data['port'] = server.server_port
2351 elif self.options.server_type == SERVER_UDP_ECHO:
2352 # Used for generating the key (randomly) that encodes the "echo request"
2353 # message.
2354 random.seed()
2355 server = UDPEchoServer((host, port), UDPEchoHandler)
2356 print 'Echo UDP server started on port %d...' % server.server_port
2357 server_data['port'] = server.server_port
2358 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2359 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2360 print 'BasicAuthProxy server started on port %d...' % server.server_port
2361 server_data['port'] = server.server_port
2362 elif self.options.server_type == SERVER_FTP:
2363 my_data_dir = self.__make_data_dir()
2364
2365 # Instantiate a dummy authorizer for managing 'virtual' users
2366 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2367
2368 # Define a new user having full r/w permissions and a read-only
2369 # anonymous user
2370 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2371
2372 authorizer.add_anonymous(my_data_dir)
2373
2374 # Instantiate FTP handler class
2375 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2376 ftp_handler.authorizer = authorizer
2377
2378 # Define a customized banner (string returned when client connects)
2379 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2380 pyftpdlib.ftpserver.__ver__)
2381
2382 # Instantiate FTP server class and listen to address:port
2383 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2384 server_data['port'] = server.socket.getsockname()[1]
2385 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002386 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002387 raise testserver_base.OptionError('unknown server type' +
2388 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002389
mattm@chromium.org830a3712012-11-07 23:00:07 +00002390 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002391
mattm@chromium.org830a3712012-11-07 23:00:07 +00002392 def run_server(self):
2393 if self.__ocsp_server:
2394 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002395
mattm@chromium.org830a3712012-11-07 23:00:07 +00002396 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002397
mattm@chromium.org830a3712012-11-07 23:00:07 +00002398 if self.__ocsp_server:
2399 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002400
mattm@chromium.org830a3712012-11-07 23:00:07 +00002401 def add_options(self):
2402 testserver_base.TestServerRunner.add_options(self)
2403 self.option_parser.add_option('-f', '--ftp', action='store_const',
2404 const=SERVER_FTP, default=SERVER_HTTP,
2405 dest='server_type',
2406 help='start up an FTP server.')
2407 self.option_parser.add_option('--sync', action='store_const',
2408 const=SERVER_SYNC, default=SERVER_HTTP,
2409 dest='server_type',
2410 help='start up a sync server.')
2411 self.option_parser.add_option('--tcp-echo', action='store_const',
2412 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2413 dest='server_type',
2414 help='start up a tcp echo server.')
2415 self.option_parser.add_option('--udp-echo', action='store_const',
2416 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2417 dest='server_type',
2418 help='start up a udp echo server.')
2419 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2420 const=SERVER_BASIC_AUTH_PROXY,
2421 default=SERVER_HTTP, dest='server_type',
2422 help='start up a proxy server which requires '
2423 'basic authentication.')
2424 self.option_parser.add_option('--websocket', action='store_const',
2425 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2426 dest='server_type',
2427 help='start up a WebSocket server.')
2428 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2429 help='Port used by the XMPP server. If '
2430 'unspecified, the XMPP server will listen on '
2431 'an ephemeral port.')
2432 self.option_parser.add_option('--data-dir', dest='data_dir',
2433 help='Directory from which to read the '
2434 'files.')
2435 self.option_parser.add_option('--https', action='store_true',
2436 dest='https', help='Specify that https '
2437 'should be used.')
2438 self.option_parser.add_option('--cert-and-key-file',
2439 dest='cert_and_key_file', help='specify the '
2440 'path to the file containing the certificate '
2441 'and private key for the server in PEM '
2442 'format')
2443 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2444 help='The type of OCSP response generated '
2445 'for the automatically generated '
2446 'certificate. One of [ok,revoked,invalid]')
2447 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2448 default='0', type='int',
2449 help='If nonzero, certain TLS connections '
2450 'will be aborted in order to test version '
2451 'fallback. 1 means all TLS versions will be '
2452 'aborted. 2 means TLS 1.1 or higher will be '
2453 'aborted. 3 means TLS 1.2 or higher will be '
2454 'aborted.')
2455 self.option_parser.add_option('--https-record-resume',
2456 dest='record_resume', const=True,
2457 default=False, action='store_const',
2458 help='Record resumption cache events rather '
2459 'than resuming as normal. Allows the use of '
2460 'the /ssl-session-cache request')
2461 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2462 help='Require SSL client auth on every '
2463 'connection.')
2464 self.option_parser.add_option('--ssl-client-ca', action='append',
2465 default=[], help='Specify that the client '
2466 'certificate request should include the CA '
2467 'named in the subject of the DER-encoded '
2468 'certificate contained in the specified '
2469 'file. This option may appear multiple '
2470 'times, indicating multiple CA names should '
2471 'be sent in the request.')
2472 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2473 help='Specify the bulk encryption '
2474 'algorithm(s) that will be accepted by the '
2475 'SSL server. Valid values are "aes256", '
2476 '"aes128", "3des", "rc4". If omitted, all '
2477 'algorithms will be used. This option may '
2478 'appear multiple times, indicating '
2479 'multiple algorithms should be enabled.');
2480 self.option_parser.add_option('--file-root-url', default='/files/',
2481 help='Specify a root URL for files served.')
2482 self.option_parser.add_option('--policy-key', action='append',
2483 dest='policy_keys',
2484 help='Specify a path to a PEM-encoded '
2485 'private key to use for policy signing. May '
2486 'be specified multiple times in order to '
2487 'load multipe keys into the server. If the '
2488 'server has multiple keys, it will rotate '
2489 'through them in at each request a '
2490 'round-robin fashion. The server will '
2491 'generate a random key if none is specified '
2492 'on the command line.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002493 self.option_parser.add_option('--auth-token', dest='auth_token',
2494 help='Specify the auth token which should be '
2495 'used in the authorization header for GData.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002496
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002497
initial.commit94958cf2008-07-26 22:42:52 +00002498if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002499 sys.exit(ServerRunner().main())