blob: ba74b69130c6f765f798dba924b5b5962a0a1145 [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
toyoshim@chromium.orgc960f6d2012-11-06 07:58:37 +000040from mod_pywebsocket.standalone import WebSocketServer
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000041import pyftpdlib.ftpserver
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +000042import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000043import tlslite
44import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000045
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +000046BASE_DIR = os.path.dirname(os.path.abspath(__file__))
davidben@chromium.org06fcf202010-09-22 18:15:23 +000047
maruel@chromium.org756cf982009-03-05 12:46:38 +000048SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000049SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000050SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000051SERVER_TCP_ECHO = 3
52SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000053SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000054SERVER_WEBSOCKET = 6
55
56# Default request queue size for WebSocketServer.
57_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000058
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +000059
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000060# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000061debug_output = sys.stderr
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +000062def debug(string):
63 debug_output.write(string + "\n")
initial.commit94958cf2008-07-26 22:42:52 +000064 debug_output.flush()
65
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +000066
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000067class WebSocketOptions:
68 """Holds options for WebSocketServer."""
69
70 def __init__(self, host, port, data_dir):
71 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
72 self.server_host = host
73 self.port = port
74 self.websock_handlers = data_dir
75 self.scan_dir = None
76 self.allow_handlers_outside_root_dir = False
77 self.websock_handlers_map_file = None
78 self.cgi_directories = []
79 self.is_executable_method = None
80 self.allow_draft75 = False
81 self.strict = True
82
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000083 self.use_tls = False
84 self.private_key = None
85 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000086 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000087 self.tls_client_ca = None
88 self.use_basic_auth = False
89
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +000090
agl@chromium.orgf9e66792011-12-12 22:22:19 +000091class RecordingSSLSessionCache(object):
92 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
93 lookups and inserts in order to test session cache behaviours."""
94
95 def __init__(self):
96 self.log = []
97
98 def __getitem__(self, sessionID):
99 self.log.append(('lookup', sessionID))
100 raise KeyError()
101
102 def __setitem__(self, sessionID, session):
103 self.log.append(('insert', sessionID))
104
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000105
106class ClientRestrictingServerMixIn:
107 """Implements verify_request to limit connections to our configured IP
108 address."""
109
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000110 def verify_request(self, _request, client_address):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000111 return client_address[0] == self.server_address[0]
112
113
initial.commit94958cf2008-07-26 22:42:52 +0000114class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000115 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000116 to be exited cleanly (by setting its "stop" member to True)."""
117
118 def serve_forever(self):
119 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000120 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000121 while not self.stop:
122 self.handle_request()
123 self.socket.close()
124
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000125
126class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000127 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000128 verification."""
129
130 pass
131
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
133 """This is a specialization of HTTPServer that serves an
134 OCSP response"""
135
136 def serve_forever_on_thread(self):
137 self.thread = threading.Thread(target = self.serve_forever,
138 name = "OCSPServerThread")
139 self.thread.start()
140
141 def stop_serving(self):
142 self.shutdown()
143 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000144
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +0000145
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000146class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
147 ClientRestrictingServerMixIn,
148 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000149 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000150 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000151
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000152 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000153 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000154 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000155 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
156 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000157 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000158 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000159 self.tls_intolerant = tls_intolerant
160
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000161 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000162 s = open(ca_file).read()
163 x509 = tlslite.api.X509()
164 x509.parse(s)
165 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000166 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
167 if ssl_bulk_ciphers is not None:
168 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000169
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000170 if record_resume_info:
171 # If record_resume_info is true then we'll replace the session cache with
172 # an object that records the lookups and inserts that it sees.
173 self.session_cache = RecordingSSLSessionCache()
174 else:
175 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000176 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
177
178 def handshake(self, tlsConnection):
179 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000180
initial.commit94958cf2008-07-26 22:42:52 +0000181 try:
182 tlsConnection.handshakeServer(certChain=self.cert_chain,
183 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000184 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000185 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000186 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000187 reqCAs=self.ssl_client_cas,
188 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000189 tlsConnection.ignoreAbruptClose = True
190 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000191 except tlslite.api.TLSAbruptCloseError:
192 # Ignore abrupt close.
193 return True
initial.commit94958cf2008-07-26 22:42:52 +0000194 except tlslite.api.TLSError, error:
195 print "Handshake failure:", str(error)
196 return False
197
akalin@chromium.org154bb132010-11-12 02:20:27 +0000198
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000199class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000200 """An HTTP server that handles sync commands."""
201
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000202 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000203 # We import here to avoid pulling in chromiumsync's dependencies
204 # unless strictly necessary.
205 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000206 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000207 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000208 self._sync_handler = chromiumsync.TestServer()
209 self._xmpp_socket_map = {}
210 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000211 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000212 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000213 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000214
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000215 def GetXmppServer(self):
216 return self._xmpp_server
217
akalin@chromium.org154bb132010-11-12 02:20:27 +0000218 def HandleCommand(self, query, raw_request):
219 return self._sync_handler.HandleCommand(query, raw_request)
220
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000221 def HandleRequestNoBlock(self):
222 """Handles a single request.
223
224 Copied from SocketServer._handle_request_noblock().
225 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000226
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000227 try:
228 request, client_address = self.get_request()
229 except socket.error:
230 return
231 if self.verify_request(request, client_address):
232 try:
233 self.process_request(request, client_address)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000234 except Exception:
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000235 self.handle_error(request, client_address)
236 self.close_request(request)
237
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000238 def SetAuthenticated(self, auth_valid):
239 self.authenticated = auth_valid
240
241 def GetAuthenticated(self):
242 return self.authenticated
243
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000244 def serve_forever(self):
245 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
246 """
247
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000248 def HandleXmppSocket(fd, socket_map, handler):
249 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000250
251 Adapted from asyncore.read() et al.
252 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000253
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000254 xmpp_connection = socket_map.get(fd)
255 # This could happen if a previous handler call caused fd to get
256 # removed from socket_map.
257 if xmpp_connection is None:
258 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000259 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000260 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000261 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
262 raise
263 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000264 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000265
266 while True:
267 read_fds = [ self.fileno() ]
268 write_fds = []
269 exceptional_fds = []
270
271 for fd, xmpp_connection in self._xmpp_socket_map.items():
272 is_r = xmpp_connection.readable()
273 is_w = xmpp_connection.writable()
274 if is_r:
275 read_fds.append(fd)
276 if is_w:
277 write_fds.append(fd)
278 if is_r or is_w:
279 exceptional_fds.append(fd)
280
281 try:
282 read_fds, write_fds, exceptional_fds = (
283 select.select(read_fds, write_fds, exceptional_fds))
284 except select.error, err:
285 if err.args[0] != errno.EINTR:
286 raise
287 else:
288 continue
289
290 for fd in read_fds:
291 if fd == self.fileno():
292 self.HandleRequestNoBlock()
293 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000294 HandleXmppSocket(fd, self._xmpp_socket_map,
295 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000296
297 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000298 HandleXmppSocket(fd, self._xmpp_socket_map,
299 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000300
301 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000302 HandleXmppSocket(fd, self._xmpp_socket_map,
303 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000304
akalin@chromium.org154bb132010-11-12 02:20:27 +0000305
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000306class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
307 """This is a specialization of FTPServer that adds client verification."""
308
309 pass
310
311
312class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000313 """A TCP echo server that echoes back what it has received."""
314
315 def server_bind(self):
316 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000317
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000318 SocketServer.TCPServer.server_bind(self)
319 host, port = self.socket.getsockname()[:2]
320 self.server_name = socket.getfqdn(host)
321 self.server_port = port
322
323 def serve_forever(self):
324 self.stop = False
325 self.nonce_time = None
326 while not self.stop:
327 self.handle_request()
328 self.socket.close()
329
330
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000331class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000332 """A UDP echo server that echoes back what it has received."""
333
334 def server_bind(self):
335 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000336
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000337 SocketServer.UDPServer.server_bind(self)
338 host, port = self.socket.getsockname()[:2]
339 self.server_name = socket.getfqdn(host)
340 self.server_port = port
341
342 def serve_forever(self):
343 self.stop = False
344 self.nonce_time = None
345 while not self.stop:
346 self.handle_request()
347 self.socket.close()
348
349
akalin@chromium.org154bb132010-11-12 02:20:27 +0000350class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
351
352 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000353 connect_handlers, get_handlers, head_handlers, post_handlers,
354 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000355 self._connect_handlers = connect_handlers
356 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000357 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000358 self._post_handlers = post_handlers
359 self._put_handlers = put_handlers
360 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
361 self, request, client_address, socket_server)
362
363 def log_request(self, *args, **kwargs):
364 # Disable request logging to declutter test log output.
365 pass
366
367 def _ShouldHandleRequest(self, handler_name):
368 """Determines if the path can be handled by the handler.
369
370 We consider a handler valid if the path begins with the
371 handler name. It can optionally be followed by "?*", "/*".
372 """
373
374 pattern = re.compile('%s($|\?|/).*' % handler_name)
375 return pattern.match(self.path)
376
377 def do_CONNECT(self):
378 for handler in self._connect_handlers:
379 if handler():
380 return
381
382 def do_GET(self):
383 for handler in self._get_handlers:
384 if handler():
385 return
386
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000387 def do_HEAD(self):
388 for handler in self._head_handlers:
389 if handler():
390 return
391
akalin@chromium.org154bb132010-11-12 02:20:27 +0000392 def do_POST(self):
393 for handler in self._post_handlers:
394 if handler():
395 return
396
397 def do_PUT(self):
398 for handler in self._put_handlers:
399 if handler():
400 return
401
402
403class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000404
405 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000406 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000407 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000408 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000409 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000410 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000411 self.NoCacheMaxAgeTimeHandler,
412 self.NoCacheTimeHandler,
413 self.CacheTimeHandler,
414 self.CacheExpiresHandler,
415 self.CacheProxyRevalidateHandler,
416 self.CachePrivateHandler,
417 self.CachePublicHandler,
418 self.CacheSMaxAgeHandler,
419 self.CacheMustRevalidateHandler,
420 self.CacheMustRevalidateMaxAgeHandler,
421 self.CacheNoStoreHandler,
422 self.CacheNoStoreMaxAgeHandler,
423 self.CacheNoTransformHandler,
424 self.DownloadHandler,
425 self.DownloadFinishHandler,
426 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000427 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000428 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000429 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000430 self.GDataAuthHandler,
431 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000432 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000433 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000434 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000435 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000436 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000437 self.AuthBasicHandler,
438 self.AuthDigestHandler,
439 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000440 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000442 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.ServerRedirectHandler,
444 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000445 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000446 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000447 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000448 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000449 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000450 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000451 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000452 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000453 self.DeviceManagementHandler,
454 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000455 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000456 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000457 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000458 head_handlers = [
459 self.FileHandler,
460 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000461
maruel@google.come250a9b2009-03-10 17:39:46 +0000462 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000463 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000464 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000465 'gif': 'image/gif',
466 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000467 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000468 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000469 'pdf' : 'application/pdf',
470 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000471 }
initial.commit94958cf2008-07-26 22:42:52 +0000472 self._default_mime_type = 'text/html'
473
akalin@chromium.org154bb132010-11-12 02:20:27 +0000474 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000475 connect_handlers, get_handlers, head_handlers,
476 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000477
initial.commit94958cf2008-07-26 22:42:52 +0000478 def GetMIMETypeFromName(self, file_name):
479 """Returns the mime type for the specified file_name. So far it only looks
480 at the file extension."""
481
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000482 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000483 if len(extension) == 0:
484 # no extension.
485 return self._default_mime_type
486
ericroman@google.comc17ca532009-05-07 03:51:05 +0000487 # extension starts with a dot, so we need to remove it
488 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000489
initial.commit94958cf2008-07-26 22:42:52 +0000490 def NoCacheMaxAgeTimeHandler(self):
491 """This request handler yields a page with the title set to the current
492 system time, and no caching requested."""
493
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000494 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000495 return False
496
497 self.send_response(200)
498 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000499 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000500 self.end_headers()
501
maruel@google.come250a9b2009-03-10 17:39:46 +0000502 self.wfile.write('<html><head><title>%s</title></head></html>' %
503 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000504
505 return True
506
507 def NoCacheTimeHandler(self):
508 """This request handler yields a page with the title set to the current
509 system time, and no caching requested."""
510
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000511 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000512 return False
513
514 self.send_response(200)
515 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000516 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000517 self.end_headers()
518
maruel@google.come250a9b2009-03-10 17:39:46 +0000519 self.wfile.write('<html><head><title>%s</title></head></html>' %
520 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000521
522 return True
523
524 def CacheTimeHandler(self):
525 """This request handler yields a page with the title set to the current
526 system time, and allows caching for one minute."""
527
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000528 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000529 return False
530
531 self.send_response(200)
532 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000533 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000534 self.end_headers()
535
maruel@google.come250a9b2009-03-10 17:39:46 +0000536 self.wfile.write('<html><head><title>%s</title></head></html>' %
537 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000538
539 return True
540
541 def CacheExpiresHandler(self):
542 """This request handler yields a page with the title set to the current
543 system time, and set the page to expire on 1 Jan 2099."""
544
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000545 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000546 return False
547
548 self.send_response(200)
549 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000550 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000551 self.end_headers()
552
maruel@google.come250a9b2009-03-10 17:39:46 +0000553 self.wfile.write('<html><head><title>%s</title></head></html>' %
554 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000555
556 return True
557
558 def CacheProxyRevalidateHandler(self):
559 """This request handler yields a page with the title set to the current
560 system time, and allows caching for 60 seconds"""
561
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000562 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000563 return False
564
565 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000566 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000567 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
568 self.end_headers()
569
maruel@google.come250a9b2009-03-10 17:39:46 +0000570 self.wfile.write('<html><head><title>%s</title></head></html>' %
571 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000572
573 return True
574
575 def CachePrivateHandler(self):
576 """This request handler yields a page with the title set to the current
577 system time, and allows caching for 5 seconds."""
578
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000579 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000580 return False
581
582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000583 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000584 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000585 self.end_headers()
586
maruel@google.come250a9b2009-03-10 17:39:46 +0000587 self.wfile.write('<html><head><title>%s</title></head></html>' %
588 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000589
590 return True
591
592 def CachePublicHandler(self):
593 """This request handler yields a page with the title set to the current
594 system time, and allows caching for 5 seconds."""
595
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000596 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000597 return False
598
599 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000600 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000601 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000602 self.end_headers()
603
maruel@google.come250a9b2009-03-10 17:39:46 +0000604 self.wfile.write('<html><head><title>%s</title></head></html>' %
605 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000606
607 return True
608
609 def CacheSMaxAgeHandler(self):
610 """This request handler yields a page with the title set to the current
611 system time, and does not allow for caching."""
612
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000613 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000614 return False
615
616 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000617 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000618 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
619 self.end_headers()
620
maruel@google.come250a9b2009-03-10 17:39:46 +0000621 self.wfile.write('<html><head><title>%s</title></head></html>' %
622 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000623
624 return True
625
626 def CacheMustRevalidateHandler(self):
627 """This request handler yields a page with the title set to the current
628 system time, and does not allow caching."""
629
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000630 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000631 return False
632
633 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000634 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000635 self.send_header('Cache-Control', 'must-revalidate')
636 self.end_headers()
637
maruel@google.come250a9b2009-03-10 17:39:46 +0000638 self.wfile.write('<html><head><title>%s</title></head></html>' %
639 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000640
641 return True
642
643 def CacheMustRevalidateMaxAgeHandler(self):
644 """This request handler yields a page with the title set to the current
645 system time, and does not allow caching event though max-age of 60
646 seconds is specified."""
647
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000648 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000649 return False
650
651 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000652 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000653 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
654 self.end_headers()
655
maruel@google.come250a9b2009-03-10 17:39:46 +0000656 self.wfile.write('<html><head><title>%s</title></head></html>' %
657 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000658
659 return True
660
initial.commit94958cf2008-07-26 22:42:52 +0000661 def CacheNoStoreHandler(self):
662 """This request handler yields a page with the title set to the current
663 system time, and does not allow the page to be stored."""
664
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000665 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000666 return False
667
668 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000669 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000670 self.send_header('Cache-Control', 'no-store')
671 self.end_headers()
672
maruel@google.come250a9b2009-03-10 17:39:46 +0000673 self.wfile.write('<html><head><title>%s</title></head></html>' %
674 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000675
676 return True
677
678 def CacheNoStoreMaxAgeHandler(self):
679 """This request handler yields a page with the title set to the current
680 system time, and does not allow the page to be stored even though max-age
681 of 60 seconds is specified."""
682
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000683 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000684 return False
685
686 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000687 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000688 self.send_header('Cache-Control', 'max-age=60, no-store')
689 self.end_headers()
690
maruel@google.come250a9b2009-03-10 17:39:46 +0000691 self.wfile.write('<html><head><title>%s</title></head></html>' %
692 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000693
694 return True
695
696
697 def CacheNoTransformHandler(self):
698 """This request handler yields a page with the title set to the current
699 system time, and does not allow the content to transformed during
700 user-agent caching"""
701
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000702 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000703 return False
704
705 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000706 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000707 self.send_header('Cache-Control', 'no-transform')
708 self.end_headers()
709
maruel@google.come250a9b2009-03-10 17:39:46 +0000710 self.wfile.write('<html><head><title>%s</title></head></html>' %
711 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000712
713 return True
714
715 def EchoHeader(self):
716 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000717
ananta@chromium.org219b2062009-10-23 16:09:41 +0000718 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000719
ananta@chromium.org56812d02011-04-07 17:52:05 +0000720 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000721 """This function echoes back the value of a specific request header while
722 allowing caching for 16 hours."""
723
ananta@chromium.org56812d02011-04-07 17:52:05 +0000724 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000725
726 def EchoHeaderHelper(self, echo_header):
727 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000728
ananta@chromium.org219b2062009-10-23 16:09:41 +0000729 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000730 return False
731
732 query_char = self.path.find('?')
733 if query_char != -1:
734 header_name = self.path[query_char+1:]
735
736 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000737 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000738 if echo_header == '/echoheadercache':
739 self.send_header('Cache-control', 'max-age=60000')
740 else:
741 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000742 # insert a vary header to properly indicate that the cachability of this
743 # request is subject to value of the request header being echoed.
744 if len(header_name) > 0:
745 self.send_header('Vary', header_name)
746 self.end_headers()
747
748 if len(header_name) > 0:
749 self.wfile.write(self.headers.getheader(header_name))
750
751 return True
752
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000753 def ReadRequestBody(self):
754 """This function reads the body of the current HTTP request, handling
755 both plain and chunked transfer encoded requests."""
756
757 if self.headers.getheader('transfer-encoding') != 'chunked':
758 length = int(self.headers.getheader('content-length'))
759 return self.rfile.read(length)
760
761 # Read the request body as chunks.
762 body = ""
763 while True:
764 line = self.rfile.readline()
765 length = int(line, 16)
766 if length == 0:
767 self.rfile.readline()
768 break
769 body += self.rfile.read(length)
770 self.rfile.read(2)
771 return body
772
initial.commit94958cf2008-07-26 22:42:52 +0000773 def EchoHandler(self):
774 """This handler just echoes back the payload of the request, for testing
775 form submission."""
776
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000777 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000778 return False
779
780 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000781 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000782 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000783 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000784 return True
785
786 def EchoTitleHandler(self):
787 """This handler is like Echo, but sets the page title to the request."""
788
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000789 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000790 return False
791
792 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000793 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000794 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000795 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000796 self.wfile.write('<html><head><title>')
797 self.wfile.write(request)
798 self.wfile.write('</title></head></html>')
799 return True
800
801 def EchoAllHandler(self):
802 """This handler yields a (more) human-readable page listing information
803 about the request header & contents."""
804
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000805 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000806 return False
807
808 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000809 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.end_headers()
811 self.wfile.write('<html><head><style>'
812 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
813 '</style></head><body>'
814 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000815 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000816 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000817
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000818 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000819 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000820 params = cgi.parse_qs(qs, keep_blank_values=1)
821
822 for param in params:
823 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000824
825 self.wfile.write('</pre>')
826
827 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
828
829 self.wfile.write('</body></html>')
830 return True
831
832 def DownloadHandler(self):
833 """This handler sends a downloadable file with or without reporting
834 the size (6K)."""
835
836 if self.path.startswith("/download-unknown-size"):
837 send_length = False
838 elif self.path.startswith("/download-known-size"):
839 send_length = True
840 else:
841 return False
842
843 #
844 # The test which uses this functionality is attempting to send
845 # small chunks of data to the client. Use a fairly large buffer
846 # so that we'll fill chrome's IO buffer enough to force it to
847 # actually write the data.
848 # See also the comments in the client-side of this test in
849 # download_uitest.cc
850 #
851 size_chunk1 = 35*1024
852 size_chunk2 = 10*1024
853
854 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000855 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000856 self.send_header('Cache-Control', 'max-age=0')
857 if send_length:
858 self.send_header('Content-Length', size_chunk1 + size_chunk2)
859 self.end_headers()
860
861 # First chunk of data:
862 self.wfile.write("*" * size_chunk1)
863 self.wfile.flush()
864
865 # handle requests until one of them clears this flag.
866 self.server.waitForDownload = True
867 while self.server.waitForDownload:
868 self.server.handle_request()
869
870 # Second chunk of data:
871 self.wfile.write("*" * size_chunk2)
872 return True
873
874 def DownloadFinishHandler(self):
875 """This handler just tells the server to finish the current download."""
876
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000877 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000878 return False
879
880 self.server.waitForDownload = False
881 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000882 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000883 self.send_header('Cache-Control', 'max-age=0')
884 self.end_headers()
885 return True
886
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000887 def _ReplaceFileData(self, data, query_parameters):
888 """Replaces matching substrings in a file.
889
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000890 If the 'replace_text' URL query parameter is present, it is expected to be
891 of the form old_text:new_text, which indicates that any old_text strings in
892 the file are replaced with new_text. Multiple 'replace_text' parameters may
893 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000894
895 If the parameters are not present, |data| is returned.
896 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000897
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000898 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000899 replace_text_values = query_dict.get('replace_text', [])
900 for replace_text_value in replace_text_values:
901 replace_text_args = replace_text_value.split(':')
902 if len(replace_text_args) != 2:
903 raise ValueError(
904 'replace_text must be of form old_text:new_text. Actual value: %s' %
905 replace_text_value)
906 old_text_b64, new_text_b64 = replace_text_args
907 old_text = base64.urlsafe_b64decode(old_text_b64)
908 new_text = base64.urlsafe_b64decode(new_text_b64)
909 data = data.replace(old_text, new_text)
910 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000911
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000912 def ZipFileHandler(self):
913 """This handler sends the contents of the requested file in compressed form.
914 Can pass in a parameter that specifies that the content length be
915 C - the compressed size (OK),
916 U - the uncompressed size (Non-standard, but handled),
917 S - less than compressed (OK because we keep going),
918 M - larger than compressed but less than uncompressed (an error),
919 L - larger than uncompressed (an error)
920 Example: compressedfiles/Picture_1.doc?C
921 """
922
923 prefix = "/compressedfiles/"
924 if not self.path.startswith(prefix):
925 return False
926
927 # Consume a request body if present.
928 if self.command == 'POST' or self.command == 'PUT' :
929 self.ReadRequestBody()
930
931 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
932
933 if not query in ('C', 'U', 'S', 'M', 'L'):
934 return False
935
936 sub_path = url_path[len(prefix):]
937 entries = sub_path.split('/')
938 file_path = os.path.join(self.server.data_dir, *entries)
939 if os.path.isdir(file_path):
940 file_path = os.path.join(file_path, 'index.html')
941
942 if not os.path.isfile(file_path):
943 print "File not found " + sub_path + " full path:" + file_path
944 self.send_error(404)
945 return True
946
947 f = open(file_path, "rb")
948 data = f.read()
949 uncompressed_len = len(data)
950 f.close()
951
952 # Compress the data.
953 data = zlib.compress(data)
954 compressed_len = len(data)
955
956 content_length = compressed_len
957 if query == 'U':
958 content_length = uncompressed_len
959 elif query == 'S':
960 content_length = compressed_len / 2
961 elif query == 'M':
962 content_length = (compressed_len + uncompressed_len) / 2
963 elif query == 'L':
964 content_length = compressed_len + uncompressed_len
965
966 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000967 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000968 self.send_header('Content-encoding', 'deflate')
969 self.send_header('Connection', 'close')
970 self.send_header('Content-Length', content_length)
971 self.send_header('ETag', '\'' + file_path + '\'')
972 self.end_headers()
973
974 self.wfile.write(data)
975
976 return True
977
initial.commit94958cf2008-07-26 22:42:52 +0000978 def FileHandler(self):
979 """This handler sends the contents of the requested file. Wow, it's like
980 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000981
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000982 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000983 if not self.path.startswith(prefix):
984 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000985 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000986
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000987 def PostOnlyFileHandler(self):
988 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000989
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000990 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000991 if not self.path.startswith(prefix):
992 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000993 return self._FileHandlerHelper(prefix)
994
995 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000996 request_body = ''
997 if self.command == 'POST' or self.command == 'PUT':
998 # Consume a request body if present.
999 request_body = self.ReadRequestBody()
1000
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001001 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001002 query_dict = cgi.parse_qs(query)
1003
1004 expected_body = query_dict.get('expected_body', [])
1005 if expected_body and request_body not in expected_body:
1006 self.send_response(404)
1007 self.end_headers()
1008 self.wfile.write('')
1009 return True
1010
1011 expected_headers = query_dict.get('expected_headers', [])
1012 for expected_header in expected_headers:
1013 header_name, expected_value = expected_header.split(':')
1014 if self.headers.getheader(header_name) != expected_value:
1015 self.send_response(404)
1016 self.end_headers()
1017 self.wfile.write('')
1018 return True
1019
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001020 sub_path = url_path[len(prefix):]
1021 entries = sub_path.split('/')
1022 file_path = os.path.join(self.server.data_dir, *entries)
1023 if os.path.isdir(file_path):
1024 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001025
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001026 if not os.path.isfile(file_path):
1027 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001028 self.send_error(404)
1029 return True
1030
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001031 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001032 data = f.read()
1033 f.close()
1034
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001035 data = self._ReplaceFileData(data, query)
1036
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001037 old_protocol_version = self.protocol_version
1038
initial.commit94958cf2008-07-26 22:42:52 +00001039 # If file.mock-http-headers exists, it contains the headers we
1040 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001041 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001042 if os.path.isfile(headers_path):
1043 f = open(headers_path, "r")
1044
1045 # "HTTP/1.1 200 OK"
1046 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001047 http_major, http_minor, status_code = re.findall(
1048 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1049 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001050 self.send_response(int(status_code))
1051
1052 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001053 header_values = re.findall('(\S+):\s*(.*)', line)
1054 if len(header_values) > 0:
1055 # "name: value"
1056 name, value = header_values[0]
1057 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001058 f.close()
1059 else:
1060 # Could be more generic once we support mime-type sniffing, but for
1061 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001062
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001063 range_header = self.headers.get('Range')
1064 if range_header and range_header.startswith('bytes='):
1065 # Note this doesn't handle all valid byte range_header values (i.e.
1066 # left open ended ones), just enough for what we needed so far.
1067 range_header = range_header[6:].split('-')
1068 start = int(range_header[0])
1069 if range_header[1]:
1070 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001071 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001072 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001073
1074 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001075 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1076 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001077 self.send_header('Content-Range', content_range)
1078 data = data[start: end + 1]
1079 else:
1080 self.send_response(200)
1081
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001082 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001083 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001084 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001085 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001086 self.end_headers()
1087
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001088 if (self.command != 'HEAD'):
1089 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001090
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001091 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001092 return True
1093
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001094 def SetCookieHandler(self):
1095 """This handler just sets a cookie, for testing cookie handling."""
1096
1097 if not self._ShouldHandleRequest("/set-cookie"):
1098 return False
1099
1100 query_char = self.path.find('?')
1101 if query_char != -1:
1102 cookie_values = self.path[query_char + 1:].split('&')
1103 else:
1104 cookie_values = ("",)
1105 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001106 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001107 for cookie_value in cookie_values:
1108 self.send_header('Set-Cookie', '%s' % cookie_value)
1109 self.end_headers()
1110 for cookie_value in cookie_values:
1111 self.wfile.write('%s' % cookie_value)
1112 return True
1113
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001114 def SetManyCookiesHandler(self):
1115 """This handler just sets a given number of cookies, for testing handling
1116 of large numbers of cookies."""
1117
1118 if not self._ShouldHandleRequest("/set-many-cookies"):
1119 return False
1120
1121 query_char = self.path.find('?')
1122 if query_char != -1:
1123 num_cookies = int(self.path[query_char + 1:])
1124 else:
1125 num_cookies = 0
1126 self.send_response(200)
1127 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001128 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001129 self.send_header('Set-Cookie', 'a=')
1130 self.end_headers()
1131 self.wfile.write('%d cookies were sent' % num_cookies)
1132 return True
1133
mattm@chromium.org983fc462012-06-30 00:52:08 +00001134 def ExpectAndSetCookieHandler(self):
1135 """Expects some cookies to be sent, and if they are, sets more cookies.
1136
1137 The expect parameter specifies a required cookie. May be specified multiple
1138 times.
1139 The set parameter specifies a cookie to set if all required cookies are
1140 preset. May be specified multiple times.
1141 The data parameter specifies the response body data to be returned."""
1142
1143 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1144 return False
1145
1146 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1147 query_dict = cgi.parse_qs(query)
1148 cookies = set()
1149 if 'Cookie' in self.headers:
1150 cookie_header = self.headers.getheader('Cookie')
1151 cookies.update([s.strip() for s in cookie_header.split(';')])
1152 got_all_expected_cookies = True
1153 for expected_cookie in query_dict.get('expect', []):
1154 if expected_cookie not in cookies:
1155 got_all_expected_cookies = False
1156 self.send_response(200)
1157 self.send_header('Content-Type', 'text/html')
1158 if got_all_expected_cookies:
1159 for cookie_value in query_dict.get('set', []):
1160 self.send_header('Set-Cookie', '%s' % cookie_value)
1161 self.end_headers()
1162 for data_value in query_dict.get('data', []):
1163 self.wfile.write(data_value)
1164 return True
1165
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001166 def SetHeaderHandler(self):
1167 """This handler sets a response header. Parameters are in the
1168 key%3A%20value&key2%3A%20value2 format."""
1169
1170 if not self._ShouldHandleRequest("/set-header"):
1171 return False
1172
1173 query_char = self.path.find('?')
1174 if query_char != -1:
1175 headers_values = self.path[query_char + 1:].split('&')
1176 else:
1177 headers_values = ("",)
1178 self.send_response(200)
1179 self.send_header('Content-Type', 'text/html')
1180 for header_value in headers_values:
1181 header_value = urllib.unquote(header_value)
1182 (key, value) = header_value.split(': ', 1)
1183 self.send_header(key, value)
1184 self.end_headers()
1185 for header_value in headers_values:
1186 self.wfile.write('%s' % header_value)
1187 return True
1188
initial.commit94958cf2008-07-26 22:42:52 +00001189 def AuthBasicHandler(self):
1190 """This handler tests 'Basic' authentication. It just sends a page with
1191 title 'user/pass' if you succeed."""
1192
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001193 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001194 return False
1195
1196 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001197 expected_password = 'secret'
1198 realm = 'testrealm'
1199 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001200
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001201 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1202 query_params = cgi.parse_qs(query, True)
1203 if 'set-cookie-if-challenged' in query_params:
1204 set_cookie_if_challenged = True
1205 if 'password' in query_params:
1206 expected_password = query_params['password'][0]
1207 if 'realm' in query_params:
1208 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001209
initial.commit94958cf2008-07-26 22:42:52 +00001210 auth = self.headers.getheader('authorization')
1211 try:
1212 if not auth:
1213 raise Exception('no auth')
1214 b64str = re.findall(r'Basic (\S+)', auth)[0]
1215 userpass = base64.b64decode(b64str)
1216 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001217 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001218 raise Exception('wrong password')
1219 except Exception, e:
1220 # Authentication failed.
1221 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001222 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001223 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001224 if set_cookie_if_challenged:
1225 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001226 self.end_headers()
1227 self.wfile.write('<html><head>')
1228 self.wfile.write('<title>Denied: %s</title>' % e)
1229 self.wfile.write('</head><body>')
1230 self.wfile.write('auth=%s<p>' % auth)
1231 self.wfile.write('b64str=%s<p>' % b64str)
1232 self.wfile.write('username: %s<p>' % username)
1233 self.wfile.write('userpass: %s<p>' % userpass)
1234 self.wfile.write('password: %s<p>' % password)
1235 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1236 self.wfile.write('</body></html>')
1237 return True
1238
1239 # Authentication successful. (Return a cachable response to allow for
1240 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001241 old_protocol_version = self.protocol_version
1242 self.protocol_version = "HTTP/1.1"
1243
initial.commit94958cf2008-07-26 22:42:52 +00001244 if_none_match = self.headers.getheader('if-none-match')
1245 if if_none_match == "abc":
1246 self.send_response(304)
1247 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001248 elif url_path.endswith(".gif"):
1249 # Using chrome/test/data/google/logo.gif as the test image
1250 test_image_path = ['google', 'logo.gif']
1251 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1252 if not os.path.isfile(gif_path):
1253 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001254 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001255 return True
1256
1257 f = open(gif_path, "rb")
1258 data = f.read()
1259 f.close()
1260
1261 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001262 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001263 self.send_header('Cache-control', 'max-age=60000')
1264 self.send_header('Etag', 'abc')
1265 self.end_headers()
1266 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001267 else:
1268 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001269 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001270 self.send_header('Cache-control', 'max-age=60000')
1271 self.send_header('Etag', 'abc')
1272 self.end_headers()
1273 self.wfile.write('<html><head>')
1274 self.wfile.write('<title>%s/%s</title>' % (username, password))
1275 self.wfile.write('</head><body>')
1276 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001277 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001278 self.wfile.write('</body></html>')
1279
rvargas@google.com54453b72011-05-19 01:11:11 +00001280 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001281 return True
1282
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001283 def GDataAuthHandler(self):
1284 """This handler verifies the Authentication header for GData requests."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001285
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001286 if not self.server.gdata_auth_token:
1287 # --auth-token is not specified, not the test case for GData.
1288 return False
1289
1290 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1291 return False
1292
1293 if 'GData-Version' not in self.headers:
1294 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1295 return True
1296
1297 if 'Authorization' not in self.headers:
1298 self.send_error(httplib.UNAUTHORIZED)
1299 return True
1300
1301 field_prefix = 'Bearer '
1302 authorization = self.headers['Authorization']
1303 if not authorization.startswith(field_prefix):
1304 self.send_error(httplib.UNAUTHORIZED)
1305 return True
1306
1307 code = authorization[len(field_prefix):]
1308 if code != self.server.gdata_auth_token:
1309 self.send_error(httplib.UNAUTHORIZED)
1310 return True
1311
1312 return False
1313
1314 def GDataDocumentsFeedQueryHandler(self):
1315 """This handler verifies if required parameters are properly
1316 specified for the GData DocumentsFeed request."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001317
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001318 if not self.server.gdata_auth_token:
1319 # --auth-token is not specified, not the test case for GData.
1320 return False
1321
1322 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1323 return False
1324
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001325 (_path, _question, query_params) = self.path.partition('?')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001326 self.query_params = urlparse.parse_qs(query_params)
1327
1328 if 'v' not in self.query_params:
1329 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1330 return True
1331 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1332 # currently our GData client only uses JSON format.
1333 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1334 return True
1335
1336 return False
1337
tonyg@chromium.org75054202010-03-31 22:06:10 +00001338 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001339 """Returns a nonce that's stable per request path for the server's lifetime.
1340 This is a fake implementation. A real implementation would only use a given
1341 nonce a single time (hence the name n-once). However, for the purposes of
1342 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001343
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001344 Args:
1345 force_reset: Iff set, the nonce will be changed. Useful for testing the
1346 "stale" response.
1347 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001348
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001349 if force_reset or not self.server.nonce_time:
1350 self.server.nonce_time = time.time()
1351 return hashlib.md5('privatekey%s%d' %
1352 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001353
1354 def AuthDigestHandler(self):
1355 """This handler tests 'Digest' authentication.
1356
1357 It just sends a page with title 'user/pass' if you succeed.
1358
1359 A stale response is sent iff "stale" is present in the request path.
1360 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001361
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001362 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001363 return False
1364
tonyg@chromium.org75054202010-03-31 22:06:10 +00001365 stale = 'stale' in self.path
1366 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001367 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001368 password = 'secret'
1369 realm = 'testrealm'
1370
1371 auth = self.headers.getheader('authorization')
1372 pairs = {}
1373 try:
1374 if not auth:
1375 raise Exception('no auth')
1376 if not auth.startswith('Digest'):
1377 raise Exception('not digest')
1378 # Pull out all the name="value" pairs as a dictionary.
1379 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1380
1381 # Make sure it's all valid.
1382 if pairs['nonce'] != nonce:
1383 raise Exception('wrong nonce')
1384 if pairs['opaque'] != opaque:
1385 raise Exception('wrong opaque')
1386
1387 # Check the 'response' value and make sure it matches our magic hash.
1388 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001389 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001390 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001391 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001392 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001393 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001394 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1395 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001396 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001397
1398 if pairs['response'] != response:
1399 raise Exception('wrong password')
1400 except Exception, e:
1401 # Authentication failed.
1402 self.send_response(401)
1403 hdr = ('Digest '
1404 'realm="%s", '
1405 'domain="/", '
1406 'qop="auth", '
1407 'algorithm=MD5, '
1408 'nonce="%s", '
1409 'opaque="%s"') % (realm, nonce, opaque)
1410 if stale:
1411 hdr += ', stale="TRUE"'
1412 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001413 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001414 self.end_headers()
1415 self.wfile.write('<html><head>')
1416 self.wfile.write('<title>Denied: %s</title>' % e)
1417 self.wfile.write('</head><body>')
1418 self.wfile.write('auth=%s<p>' % auth)
1419 self.wfile.write('pairs=%s<p>' % pairs)
1420 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1421 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1422 self.wfile.write('</body></html>')
1423 return True
1424
1425 # Authentication successful.
1426 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001427 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001428 self.end_headers()
1429 self.wfile.write('<html><head>')
1430 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1431 self.wfile.write('</head><body>')
1432 self.wfile.write('auth=%s<p>' % auth)
1433 self.wfile.write('pairs=%s<p>' % pairs)
1434 self.wfile.write('</body></html>')
1435
1436 return True
1437
1438 def SlowServerHandler(self):
1439 """Wait for the user suggested time before responding. The syntax is
1440 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001441
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001442 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001443 return False
1444 query_char = self.path.find('?')
1445 wait_sec = 1.0
1446 if query_char >= 0:
1447 try:
1448 wait_sec = int(self.path[query_char + 1:])
1449 except ValueError:
1450 pass
1451 time.sleep(wait_sec)
1452 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001453 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001454 self.end_headers()
1455 self.wfile.write("waited %d seconds" % wait_sec)
1456 return True
1457
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001458 def ChunkedServerHandler(self):
1459 """Send chunked response. Allows to specify chunks parameters:
1460 - waitBeforeHeaders - ms to wait before sending headers
1461 - waitBetweenChunks - ms to wait between chunks
1462 - chunkSize - size of each chunk in bytes
1463 - chunksNumber - number of chunks
1464 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1465 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001466
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001467 if not self._ShouldHandleRequest("/chunked"):
1468 return False
1469 query_char = self.path.find('?')
1470 chunkedSettings = {'waitBeforeHeaders' : 0,
1471 'waitBetweenChunks' : 0,
1472 'chunkSize' : 5,
1473 'chunksNumber' : 5}
1474 if query_char >= 0:
1475 params = self.path[query_char + 1:].split('&')
1476 for param in params:
1477 keyValue = param.split('=')
1478 if len(keyValue) == 2:
1479 try:
1480 chunkedSettings[keyValue[0]] = int(keyValue[1])
1481 except ValueError:
1482 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001483 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001484 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1485 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001486 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001487 self.send_header('Connection', 'close')
1488 self.send_header('Transfer-Encoding', 'chunked')
1489 self.end_headers()
1490 # Chunked encoding: sending all chunks, then final zero-length chunk and
1491 # then final CRLF.
1492 for i in range(0, chunkedSettings['chunksNumber']):
1493 if i > 0:
1494 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1495 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001496 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001497 self.sendChunkHelp('')
1498 return True
1499
initial.commit94958cf2008-07-26 22:42:52 +00001500 def ContentTypeHandler(self):
1501 """Returns a string of html with the given content type. E.g.,
1502 /contenttype?text/css returns an html file with the Content-Type
1503 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001504
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001505 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001506 return False
1507 query_char = self.path.find('?')
1508 content_type = self.path[query_char + 1:].strip()
1509 if not content_type:
1510 content_type = 'text/html'
1511 self.send_response(200)
1512 self.send_header('Content-Type', content_type)
1513 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001514 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001515 return True
1516
creis@google.com2f4f6a42011-03-25 19:44:19 +00001517 def NoContentHandler(self):
1518 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001519
creis@google.com2f4f6a42011-03-25 19:44:19 +00001520 if not self._ShouldHandleRequest("/nocontent"):
1521 return False
1522 self.send_response(204)
1523 self.end_headers()
1524 return True
1525
initial.commit94958cf2008-07-26 22:42:52 +00001526 def ServerRedirectHandler(self):
1527 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001528 '/server-redirect?http://foo.bar/asdf' to redirect to
1529 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001530
1531 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001532 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001533 return False
1534
1535 query_char = self.path.find('?')
1536 if query_char < 0 or len(self.path) <= query_char + 1:
1537 self.sendRedirectHelp(test_name)
1538 return True
1539 dest = self.path[query_char + 1:]
1540
1541 self.send_response(301) # moved permanently
1542 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001543 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001544 self.end_headers()
1545 self.wfile.write('<html><head>')
1546 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1547
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001548 return True
initial.commit94958cf2008-07-26 22:42:52 +00001549
1550 def ClientRedirectHandler(self):
1551 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001552 '/client-redirect?http://foo.bar/asdf' to redirect to
1553 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001554
1555 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001556 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001557 return False
1558
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001559 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001560 if query_char < 0 or len(self.path) <= query_char + 1:
1561 self.sendRedirectHelp(test_name)
1562 return True
1563 dest = self.path[query_char + 1:]
1564
1565 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001566 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001567 self.end_headers()
1568 self.wfile.write('<html><head>')
1569 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1570 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1571
1572 return True
1573
tony@chromium.org03266982010-03-05 03:18:42 +00001574 def MultipartHandler(self):
1575 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001576
tony@chromium.org4cb88302011-09-27 22:13:49 +00001577 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001578 if not self._ShouldHandleRequest(test_name):
1579 return False
1580
1581 num_frames = 10
1582 bound = '12345'
1583 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001584 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001585 'multipart/x-mixed-replace;boundary=' + bound)
1586 self.end_headers()
1587
1588 for i in xrange(num_frames):
1589 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001590 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001591 self.wfile.write('<title>page ' + str(i) + '</title>')
1592 self.wfile.write('page ' + str(i))
1593
1594 self.wfile.write('--' + bound + '--')
1595 return True
1596
tony@chromium.org4cb88302011-09-27 22:13:49 +00001597 def MultipartSlowHandler(self):
1598 """Send a multipart response (3 text/html pages) with a slight delay
1599 between each page. This is similar to how some pages show status using
1600 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001601
tony@chromium.org4cb88302011-09-27 22:13:49 +00001602 test_name = '/multipart-slow'
1603 if not self._ShouldHandleRequest(test_name):
1604 return False
1605
1606 num_frames = 3
1607 bound = '12345'
1608 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001609 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001610 'multipart/x-mixed-replace;boundary=' + bound)
1611 self.end_headers()
1612
1613 for i in xrange(num_frames):
1614 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001615 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001616 time.sleep(0.25)
1617 if i == 2:
1618 self.wfile.write('<title>PASS</title>')
1619 else:
1620 self.wfile.write('<title>page ' + str(i) + '</title>')
1621 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1622
1623 self.wfile.write('--' + bound + '--')
1624 return True
1625
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001626 def GetSSLSessionCacheHandler(self):
1627 """Send a reply containing a log of the session cache operations."""
1628
1629 if not self._ShouldHandleRequest('/ssl-session-cache'):
1630 return False
1631
1632 self.send_response(200)
1633 self.send_header('Content-Type', 'text/plain')
1634 self.end_headers()
1635 try:
1636 for (action, sessionID) in self.server.session_cache.log:
1637 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001638 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001639 self.wfile.write('Pass --https-record-resume in order to use' +
1640 ' this request')
1641 return True
1642
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001643 def CloseSocketHandler(self):
1644 """Closes the socket without sending anything."""
1645
1646 if not self._ShouldHandleRequest('/close-socket'):
1647 return False
1648
1649 self.wfile.close()
1650 return True
1651
initial.commit94958cf2008-07-26 22:42:52 +00001652 def DefaultResponseHandler(self):
1653 """This is the catch-all response handler for requests that aren't handled
1654 by one of the special handlers above.
1655 Note that we specify the content-length as without it the https connection
1656 is not closed properly (and the browser keeps expecting data)."""
1657
1658 contents = "Default response given for path: " + self.path
1659 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001660 self.send_header('Content-Type', 'text/html')
1661 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001662 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001663 if (self.command != 'HEAD'):
1664 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001665 return True
1666
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001667 def RedirectConnectHandler(self):
1668 """Sends a redirect to the CONNECT request for www.redirect.com. This
1669 response is not specified by the RFC, so the browser should not follow
1670 the redirect."""
1671
1672 if (self.path.find("www.redirect.com") < 0):
1673 return False
1674
1675 dest = "http://www.destination.com/foo.js"
1676
1677 self.send_response(302) # moved temporarily
1678 self.send_header('Location', dest)
1679 self.send_header('Connection', 'close')
1680 self.end_headers()
1681 return True
1682
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001683 def ServerAuthConnectHandler(self):
1684 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1685 response doesn't make sense because the proxy server cannot request
1686 server authentication."""
1687
1688 if (self.path.find("www.server-auth.com") < 0):
1689 return False
1690
1691 challenge = 'Basic realm="WallyWorld"'
1692
1693 self.send_response(401) # unauthorized
1694 self.send_header('WWW-Authenticate', challenge)
1695 self.send_header('Connection', 'close')
1696 self.end_headers()
1697 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001698
1699 def DefaultConnectResponseHandler(self):
1700 """This is the catch-all response handler for CONNECT requests that aren't
1701 handled by one of the special handlers above. Real Web servers respond
1702 with 400 to CONNECT requests."""
1703
1704 contents = "Your client has issued a malformed or illegal request."
1705 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001706 self.send_header('Content-Type', 'text/html')
1707 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001708 self.end_headers()
1709 self.wfile.write(contents)
1710 return True
1711
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001712 def DeviceManagementHandler(self):
1713 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001714
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001715 if not self._ShouldHandleRequest("/device_management"):
1716 return False
1717
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001718 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001719
1720 if not self.server._device_management_handler:
1721 import device_management
1722 policy_path = os.path.join(self.server.data_dir, 'device_management')
1723 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001724 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001725 self.server.policy_keys,
1726 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001727
1728 http_response, raw_reply = (
1729 self.server._device_management_handler.HandleRequest(self.path,
1730 self.headers,
1731 raw_request))
1732 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001733 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001734 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001735 self.end_headers()
1736 self.wfile.write(raw_reply)
1737 return True
1738
initial.commit94958cf2008-07-26 22:42:52 +00001739 # called by the redirect handling function when there is no parameter
1740 def sendRedirectHelp(self, redirect_name):
1741 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001742 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001743 self.end_headers()
1744 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1745 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1746 self.wfile.write('</body></html>')
1747
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001748 # called by chunked handling function
1749 def sendChunkHelp(self, chunk):
1750 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1751 self.wfile.write('%X\r\n' % len(chunk))
1752 self.wfile.write(chunk)
1753 self.wfile.write('\r\n')
1754
akalin@chromium.org154bb132010-11-12 02:20:27 +00001755
1756class SyncPageHandler(BasePageHandler):
1757 """Handler for the main HTTP sync server."""
1758
1759 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001760 get_handlers = [self.ChromiumSyncTimeHandler,
1761 self.ChromiumSyncMigrationOpHandler,
1762 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001763 self.ChromiumSyncDisableNotificationsOpHandler,
1764 self.ChromiumSyncEnableNotificationsOpHandler,
1765 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001766 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001767 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001768 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001769 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001770 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001771
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001772 post_handlers = [self.ChromiumSyncCommandHandler,
1773 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001774 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001775 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001776 post_handlers, [])
1777
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001778
akalin@chromium.org154bb132010-11-12 02:20:27 +00001779 def ChromiumSyncTimeHandler(self):
1780 """Handle Chromium sync .../time requests.
1781
1782 The syncer sometimes checks server reachability by examining /time.
1783 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001784
akalin@chromium.org154bb132010-11-12 02:20:27 +00001785 test_name = "/chromiumsync/time"
1786 if not self._ShouldHandleRequest(test_name):
1787 return False
1788
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001789 # Chrome hates it if we send a response before reading the request.
1790 if self.headers.getheader('content-length'):
1791 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001792 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001793
akalin@chromium.org154bb132010-11-12 02:20:27 +00001794 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001795 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001796 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001797 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001798 return True
1799
1800 def ChromiumSyncCommandHandler(self):
1801 """Handle a chromiumsync command arriving via http.
1802
1803 This covers all sync protocol commands: authentication, getupdates, and
1804 commit.
1805 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001806
akalin@chromium.org154bb132010-11-12 02:20:27 +00001807 test_name = "/chromiumsync/command"
1808 if not self._ShouldHandleRequest(test_name):
1809 return False
1810
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001811 length = int(self.headers.getheader('content-length'))
1812 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001813 http_response = 200
1814 raw_reply = None
1815 if not self.server.GetAuthenticated():
1816 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001817 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1818 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001819 else:
1820 http_response, raw_reply = self.server.HandleCommand(
1821 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001822
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001823 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001824 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001825 if http_response == 401:
1826 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001827 self.end_headers()
1828 self.wfile.write(raw_reply)
1829 return True
1830
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001831 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001832 test_name = "/chromiumsync/migrate"
1833 if not self._ShouldHandleRequest(test_name):
1834 return False
1835
1836 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1837 self.path)
1838 self.send_response(http_response)
1839 self.send_header('Content-Type', 'text/html')
1840 self.send_header('Content-Length', len(raw_reply))
1841 self.end_headers()
1842 self.wfile.write(raw_reply)
1843 return True
1844
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001845 def ChromiumSyncCredHandler(self):
1846 test_name = "/chromiumsync/cred"
1847 if not self._ShouldHandleRequest(test_name):
1848 return False
1849 try:
1850 query = urlparse.urlparse(self.path)[4]
1851 cred_valid = urlparse.parse_qs(query)['valid']
1852 if cred_valid[0] == 'True':
1853 self.server.SetAuthenticated(True)
1854 else:
1855 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001856 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001857 self.server.SetAuthenticated(False)
1858
1859 http_response = 200
1860 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1861 self.send_response(http_response)
1862 self.send_header('Content-Type', 'text/html')
1863 self.send_header('Content-Length', len(raw_reply))
1864 self.end_headers()
1865 self.wfile.write(raw_reply)
1866 return True
1867
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001868 def ChromiumSyncDisableNotificationsOpHandler(self):
1869 test_name = "/chromiumsync/disablenotifications"
1870 if not self._ShouldHandleRequest(test_name):
1871 return False
1872 self.server.GetXmppServer().DisableNotifications()
1873 result = 200
1874 raw_reply = ('<html><title>Notifications disabled</title>'
1875 '<H1>Notifications disabled</H1></html>')
1876 self.send_response(result)
1877 self.send_header('Content-Type', 'text/html')
1878 self.send_header('Content-Length', len(raw_reply))
1879 self.end_headers()
1880 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001881 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001882
1883 def ChromiumSyncEnableNotificationsOpHandler(self):
1884 test_name = "/chromiumsync/enablenotifications"
1885 if not self._ShouldHandleRequest(test_name):
1886 return False
1887 self.server.GetXmppServer().EnableNotifications()
1888 result = 200
1889 raw_reply = ('<html><title>Notifications enabled</title>'
1890 '<H1>Notifications enabled</H1></html>')
1891 self.send_response(result)
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)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001896 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001897
1898 def ChromiumSyncSendNotificationOpHandler(self):
1899 test_name = "/chromiumsync/sendnotification"
1900 if not self._ShouldHandleRequest(test_name):
1901 return False
1902 query = urlparse.urlparse(self.path)[4]
1903 query_params = urlparse.parse_qs(query)
1904 channel = ''
1905 data = ''
1906 if 'channel' in query_params:
1907 channel = query_params['channel'][0]
1908 if 'data' in query_params:
1909 data = query_params['data'][0]
1910 self.server.GetXmppServer().SendNotification(channel, data)
1911 result = 200
1912 raw_reply = ('<html><title>Notification sent</title>'
1913 '<H1>Notification sent with channel "%s" '
1914 'and data "%s"</H1></html>'
1915 % (channel, data))
1916 self.send_response(result)
1917 self.send_header('Content-Type', 'text/html')
1918 self.send_header('Content-Length', len(raw_reply))
1919 self.end_headers()
1920 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001921 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001922
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001923 def ChromiumSyncBirthdayErrorOpHandler(self):
1924 test_name = "/chromiumsync/birthdayerror"
1925 if not self._ShouldHandleRequest(test_name):
1926 return False
1927 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1928 self.send_response(result)
1929 self.send_header('Content-Type', 'text/html')
1930 self.send_header('Content-Length', len(raw_reply))
1931 self.end_headers()
1932 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001933 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001934
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001935 def ChromiumSyncTransientErrorOpHandler(self):
1936 test_name = "/chromiumsync/transienterror"
1937 if not self._ShouldHandleRequest(test_name):
1938 return False
1939 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1940 self.send_response(result)
1941 self.send_header('Content-Type', 'text/html')
1942 self.send_header('Content-Length', len(raw_reply))
1943 self.end_headers()
1944 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001945 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001946
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001947 def ChromiumSyncErrorOpHandler(self):
1948 test_name = "/chromiumsync/error"
1949 if not self._ShouldHandleRequest(test_name):
1950 return False
1951 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1952 self.path)
1953 self.send_response(result)
1954 self.send_header('Content-Type', 'text/html')
1955 self.send_header('Content-Length', len(raw_reply))
1956 self.end_headers()
1957 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001958 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001959
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001960 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1961 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001962 if not self._ShouldHandleRequest(test_name):
1963 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001964 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001965 self.send_response(result)
1966 self.send_header('Content-Type', 'text/html')
1967 self.send_header('Content-Length', len(raw_reply))
1968 self.end_headers()
1969 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001970 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001971
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001972 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1973 test_name = "/chromiumsync/createsyncedbookmarks"
1974 if not self._ShouldHandleRequest(test_name):
1975 return False
1976 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1977 self.send_response(result)
1978 self.send_header('Content-Type', 'text/html')
1979 self.send_header('Content-Length', len(raw_reply))
1980 self.end_headers()
1981 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001982 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001983
akalin@chromium.org154bb132010-11-12 02:20:27 +00001984
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001985class OCSPHandler(BasePageHandler):
1986 def __init__(self, request, client_address, socket_server):
1987 handlers = [self.OCSPResponse]
1988 self.ocsp_response = socket_server.ocsp_response
1989 BasePageHandler.__init__(self, request, client_address, socket_server,
1990 [], handlers, [], handlers, [])
1991
1992 def OCSPResponse(self):
1993 self.send_response(200)
1994 self.send_header('Content-Type', 'application/ocsp-response')
1995 self.send_header('Content-Length', str(len(self.ocsp_response)))
1996 self.end_headers()
1997
1998 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001999
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002000
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002001class TCPEchoHandler(SocketServer.BaseRequestHandler):
2002 """The RequestHandler class for TCP echo server.
2003
2004 It is instantiated once per connection to the server, and overrides the
2005 handle() method to implement communication to the client.
2006 """
2007
2008 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002009 """Handles the request from the client and constructs a response."""
2010
2011 data = self.request.recv(65536).strip()
2012 # Verify the "echo request" message received from the client. Send back
2013 # "echo response" message if "echo request" message is valid.
2014 try:
2015 return_data = echo_message.GetEchoResponseData(data)
2016 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002017 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002018 except ValueError:
2019 return
2020
2021 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002022
2023
2024class UDPEchoHandler(SocketServer.BaseRequestHandler):
2025 """The RequestHandler class for UDP echo server.
2026
2027 It is instantiated once per connection to the server, and overrides the
2028 handle() method to implement communication to the client.
2029 """
2030
2031 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002032 """Handles the request from the client and constructs a response."""
2033
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002034 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002035 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002036 # Verify the "echo request" message received from the client. Send back
2037 # "echo response" message if "echo request" message is valid.
2038 try:
2039 return_data = echo_message.GetEchoResponseData(data)
2040 if not return_data:
2041 return
2042 except ValueError:
2043 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002044 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002045
2046
bashi@chromium.org33233532012-09-08 17:37:24 +00002047class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2048 """A request handler that behaves as a proxy server which requires
2049 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2050 """
2051
2052 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2053
2054 def parse_request(self):
2055 """Overrides parse_request to check credential."""
2056
2057 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2058 return False
2059
2060 auth = self.headers.getheader('Proxy-Authorization')
2061 if auth != self._AUTH_CREDENTIAL:
2062 self.send_response(407)
2063 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2064 self.end_headers()
2065 return False
2066
2067 return True
2068
2069 def _start_read_write(self, sock):
2070 sock.setblocking(0)
2071 self.request.setblocking(0)
2072 rlist = [self.request, sock]
2073 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002074 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002075 if errors:
2076 self.send_response(500)
2077 self.end_headers()
2078 return
2079 for s in ready_sockets:
2080 received = s.recv(1024)
2081 if len(received) == 0:
2082 return
2083 if s == self.request:
2084 other = sock
2085 else:
2086 other = self.request
2087 other.send(received)
2088
2089 def _do_common_method(self):
2090 url = urlparse.urlparse(self.path)
2091 port = url.port
2092 if not port:
2093 if url.scheme == 'http':
2094 port = 80
2095 elif url.scheme == 'https':
2096 port = 443
2097 if not url.hostname or not port:
2098 self.send_response(400)
2099 self.end_headers()
2100 return
2101
2102 if len(url.path) == 0:
2103 path = '/'
2104 else:
2105 path = url.path
2106 if len(url.query) > 0:
2107 path = '%s?%s' % (url.path, url.query)
2108
2109 sock = None
2110 try:
2111 sock = socket.create_connection((url.hostname, port))
2112 sock.send('%s %s %s\r\n' % (
2113 self.command, path, self.protocol_version))
2114 for header in self.headers.headers:
2115 header = header.strip()
2116 if (header.lower().startswith('connection') or
2117 header.lower().startswith('proxy')):
2118 continue
2119 sock.send('%s\r\n' % header)
2120 sock.send('\r\n')
2121 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002122 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002123 self.send_response(500)
2124 self.end_headers()
2125 finally:
2126 if sock is not None:
2127 sock.close()
2128
2129 def do_CONNECT(self):
2130 try:
2131 pos = self.path.rfind(':')
2132 host = self.path[:pos]
2133 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002134 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002135 self.send_response(400)
2136 self.end_headers()
2137
2138 try:
2139 sock = socket.create_connection((host, port))
2140 self.send_response(200, 'Connection established')
2141 self.end_headers()
2142 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002143 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002144 self.send_response(500)
2145 self.end_headers()
2146 finally:
2147 sock.close()
2148
2149 def do_GET(self):
2150 self._do_common_method()
2151
2152 def do_HEAD(self):
2153 self._do_common_method()
2154
2155
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002156class ServerRunner(testserver_base.TestServerRunner):
2157 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002158
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002159 def __init__(self):
2160 super(ServerRunner, self).__init__()
2161 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002162
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002163 def __make_data_dir(self):
2164 if self.options.data_dir:
2165 if not os.path.isdir(self.options.data_dir):
2166 raise testserver_base.OptionError('specified data dir not found: ' +
2167 self.options.data_dir + ' exiting...')
2168 my_data_dir = self.options.data_dir
2169 else:
2170 # Create the default path to our data dir, relative to the exe dir.
2171 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2172 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002173
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002174 #TODO(ibrar): Must use Find* funtion defined in google\tools
2175 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002176
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002177 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002178
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002179 def create_server(self, server_data):
2180 port = self.options.port
2181 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002182
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002183 if self.options.server_type == SERVER_HTTP:
2184 if self.options.https:
2185 pem_cert_and_key = None
2186 if self.options.cert_and_key_file:
2187 if not os.path.isfile(self.options.cert_and_key_file):
2188 raise testserver_base.OptionError(
2189 'specified server cert file not found: ' +
2190 self.options.cert_and_key_file + ' exiting...')
2191 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002192 else:
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002193 # generate a new certificate and run an OCSP server for it.
2194 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2195 print ('OCSP server started on %s:%d...' %
2196 (host, self.__ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002197
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002198 ocsp_der = None
2199 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002200
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002201 if self.options.ocsp == 'ok':
2202 ocsp_state = minica.OCSP_STATE_GOOD
2203 elif self.options.ocsp == 'revoked':
2204 ocsp_state = minica.OCSP_STATE_REVOKED
2205 elif self.options.ocsp == 'invalid':
2206 ocsp_state = minica.OCSP_STATE_INVALID
2207 elif self.options.ocsp == 'unauthorized':
2208 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2209 elif self.options.ocsp == 'unknown':
2210 ocsp_state = minica.OCSP_STATE_UNKNOWN
2211 else:
2212 raise testserver_base.OptionError('unknown OCSP status: ' +
2213 self.options.ocsp_status)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002214
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002215 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2216 subject = "127.0.0.1",
2217 ocsp_url = ("http://%s:%d/ocsp" %
2218 (host, self.__ocsp_server.server_port)),
2219 ocsp_state = ocsp_state)
2220
2221 self.__ocsp_server.ocsp_response = ocsp_der
2222
2223 for ca_cert in self.options.ssl_client_ca:
2224 if not os.path.isfile(ca_cert):
2225 raise testserver_base.OptionError(
2226 'specified trusted client CA file not found: ' + ca_cert +
2227 ' exiting...')
2228 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2229 self.options.ssl_client_auth,
2230 self.options.ssl_client_ca,
2231 self.options.ssl_bulk_cipher,
2232 self.options.record_resume,
2233 self.options.tls_intolerant)
2234 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2235 else:
2236 server = HTTPServer((host, port), TestPageHandler)
2237 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2238
2239 server.data_dir = self.__make_data_dir()
2240 server.file_root_url = self.options.file_root_url
2241 server_data['port'] = server.server_port
2242 server._device_management_handler = None
2243 server.policy_keys = self.options.policy_keys
2244 server.policy_user = self.options.policy_user
2245 server.gdata_auth_token = self.options.auth_token
2246 elif self.options.server_type == SERVER_WEBSOCKET:
2247 # Launch pywebsocket via WebSocketServer.
2248 logger = logging.getLogger()
2249 logger.addHandler(logging.StreamHandler())
2250 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2251 # is required to work correctly. It should be fixed from pywebsocket side.
2252 os.chdir(self.__make_data_dir())
2253 websocket_options = WebSocketOptions(host, port, '.')
2254 if self.options.cert_and_key_file:
2255 websocket_options.use_tls = True
2256 websocket_options.private_key = self.options.cert_and_key_file
2257 websocket_options.certificate = self.options.cert_and_key_file
2258 if self.options.ssl_client_auth:
2259 websocket_options.tls_client_auth = True
2260 if len(self.options.ssl_client_ca) != 1:
2261 raise testserver_base.OptionError(
2262 'one trusted client CA file should be specified')
2263 if not os.path.isfile(self.options.ssl_client_ca[0]):
2264 raise testserver_base.OptionError(
2265 'specified trusted client CA file not found: ' +
2266 self.options.ssl_client_ca[0] + ' exiting...')
2267 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2268 server = WebSocketServer(websocket_options)
2269 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2270 server_data['port'] = server.server_port
2271 elif self.options.server_type == SERVER_SYNC:
2272 xmpp_port = self.options.xmpp_port
2273 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2274 print 'Sync HTTP server started on port %d...' % server.server_port
2275 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2276 server_data['port'] = server.server_port
2277 server_data['xmpp_port'] = server.xmpp_port
2278 elif self.options.server_type == SERVER_TCP_ECHO:
2279 # Used for generating the key (randomly) that encodes the "echo request"
2280 # message.
2281 random.seed()
2282 server = TCPEchoServer((host, port), TCPEchoHandler)
2283 print 'Echo TCP server started on port %d...' % server.server_port
2284 server_data['port'] = server.server_port
2285 elif self.options.server_type == SERVER_UDP_ECHO:
2286 # Used for generating the key (randomly) that encodes the "echo request"
2287 # message.
2288 random.seed()
2289 server = UDPEchoServer((host, port), UDPEchoHandler)
2290 print 'Echo UDP server started on port %d...' % server.server_port
2291 server_data['port'] = server.server_port
2292 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2293 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2294 print 'BasicAuthProxy server started on port %d...' % server.server_port
2295 server_data['port'] = server.server_port
2296 elif self.options.server_type == SERVER_FTP:
2297 my_data_dir = self.__make_data_dir()
2298
2299 # Instantiate a dummy authorizer for managing 'virtual' users
2300 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2301
2302 # Define a new user having full r/w permissions and a read-only
2303 # anonymous user
2304 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2305
2306 authorizer.add_anonymous(my_data_dir)
2307
2308 # Instantiate FTP handler class
2309 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2310 ftp_handler.authorizer = authorizer
2311
2312 # Define a customized banner (string returned when client connects)
2313 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2314 pyftpdlib.ftpserver.__ver__)
2315
2316 # Instantiate FTP server class and listen to address:port
2317 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2318 server_data['port'] = server.socket.getsockname()[1]
2319 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002320 else:
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002321 raise testserver_base.OptionError('unknown server type' +
2322 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002323
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002324 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002325
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002326 def run_server(self):
2327 if self.__ocsp_server:
2328 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002329
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002330 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002331
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002332 if self.__ocsp_server:
2333 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002334
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002335 def add_options(self):
2336 testserver_base.TestServerRunner.add_options(self)
2337 self.option_parser.add_option('-f', '--ftp', action='store_const',
2338 const=SERVER_FTP, default=SERVER_HTTP,
2339 dest='server_type',
2340 help='start up an FTP server.')
2341 self.option_parser.add_option('--sync', action='store_const',
2342 const=SERVER_SYNC, default=SERVER_HTTP,
2343 dest='server_type',
2344 help='start up a sync server.')
2345 self.option_parser.add_option('--tcp-echo', action='store_const',
2346 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2347 dest='server_type',
2348 help='start up a tcp echo server.')
2349 self.option_parser.add_option('--udp-echo', action='store_const',
2350 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2351 dest='server_type',
2352 help='start up a udp echo server.')
2353 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2354 const=SERVER_BASIC_AUTH_PROXY,
2355 default=SERVER_HTTP, dest='server_type',
2356 help='start up a proxy server which requires '
2357 'basic authentication.')
2358 self.option_parser.add_option('--websocket', action='store_const',
2359 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2360 dest='server_type',
2361 help='start up a WebSocket server.')
2362 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2363 help='Port used by the XMPP server. If '
2364 'unspecified, the XMPP server will listen on '
2365 'an ephemeral port.')
2366 self.option_parser.add_option('--data-dir', dest='data_dir',
2367 help='Directory from which to read the '
2368 'files.')
2369 self.option_parser.add_option('--https', action='store_true',
2370 dest='https', help='Specify that https '
2371 'should be used.')
2372 self.option_parser.add_option('--cert-and-key-file',
2373 dest='cert_and_key_file', help='specify the '
2374 'path to the file containing the certificate '
2375 'and private key for the server in PEM '
2376 'format')
2377 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2378 help='The type of OCSP response generated '
2379 'for the automatically generated '
2380 'certificate. One of [ok,revoked,invalid]')
2381 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2382 default='0', type='int',
2383 help='If nonzero, certain TLS connections '
2384 'will be aborted in order to test version '
2385 'fallback. 1 means all TLS versions will be '
2386 'aborted. 2 means TLS 1.1 or higher will be '
2387 'aborted. 3 means TLS 1.2 or higher will be '
2388 'aborted.')
2389 self.option_parser.add_option('--https-record-resume',
2390 dest='record_resume', const=True,
2391 default=False, action='store_const',
2392 help='Record resumption cache events rather '
2393 'than resuming as normal. Allows the use of '
2394 'the /ssl-session-cache request')
2395 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2396 help='Require SSL client auth on every '
2397 'connection.')
2398 self.option_parser.add_option('--ssl-client-ca', action='append',
2399 default=[], help='Specify that the client '
2400 'certificate request should include the CA '
2401 'named in the subject of the DER-encoded '
2402 'certificate contained in the specified '
2403 'file. This option may appear multiple '
2404 'times, indicating multiple CA names should '
2405 'be sent in the request.')
2406 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2407 help='Specify the bulk encryption '
2408 'algorithm(s) that will be accepted by the '
2409 'SSL server. Valid values are "aes256", '
2410 '"aes128", "3des", "rc4". If omitted, all '
2411 'algorithms will be used. This option may '
2412 'appear multiple times, indicating '
2413 'multiple algorithms should be enabled.');
2414 self.option_parser.add_option('--file-root-url', default='/files/',
2415 help='Specify a root URL for files served.')
2416 self.option_parser.add_option('--policy-key', action='append',
2417 dest='policy_keys',
2418 help='Specify a path to a PEM-encoded '
2419 'private key to use for policy signing. May '
2420 'be specified multiple times in order to '
2421 'load multipe keys into the server. If the '
2422 'server has multiple keys, it will rotate '
2423 'through them in at each request a '
2424 'round-robin fashion. The server will '
2425 'generate a random key if none is specified '
2426 'on the command line.')
2427 self.option_parser.add_option('--policy-user',
2428 default='user@example.com',
2429 dest='policy_user',
2430 help='Specify the user name the server '
2431 'should report back to the client as the '
2432 'user owning the token used for making the '
2433 'policy request.')
2434 self.option_parser.add_option('--auth-token', dest='auth_token',
2435 help='Specify the auth token which should be '
2436 'used in the authorization header for GData.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002437
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002438
initial.commit94958cf2008-07-26 22:42:52 +00002439if __name__ == '__main__':
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002440 sys.exit(ServerRunner().main())