blob: 0ab49c07ff056732ab6bb16206397d11ae8d93f4 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000021import hashlib
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000022import httplib
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000023import json
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000024import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000025import minica
initial.commit94958cf2008-07-26 22:42:52 +000026import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000027import random
initial.commit94958cf2008-07-26 22:42:52 +000028import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000029import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000030import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import SocketServer
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000039import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000040import pyftpdlib.ftpserver
mattm@chromium.org830a3712012-11-07 23:00:07 +000041import testserver_base
initial.commit94958cf2008-07-26 22:42:52 +000042import tlslite
43import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000044
mattm@chromium.org830a3712012-11-07 23:00:07 +000045BASE_DIR = os.path.dirname(os.path.abspath(__file__))
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000046sys.path.insert(
47 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
48from mod_pywebsocket.standalone import WebSocketServer
davidben@chromium.org06fcf202010-09-22 18:15:23 +000049
maruel@chromium.org756cf982009-03-05 12:46:38 +000050SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000051SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000052SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000053SERVER_TCP_ECHO = 3
54SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000055SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000056SERVER_WEBSOCKET = 6
57
58# Default request queue size for WebSocketServer.
59_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000060
mattm@chromium.org830a3712012-11-07 23:00:07 +000061
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000062# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000063debug_output = sys.stderr
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +000064def debug(string):
65 debug_output.write(string + "\n")
initial.commit94958cf2008-07-26 22:42:52 +000066 debug_output.flush()
67
mattm@chromium.org830a3712012-11-07 23:00:07 +000068
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000069class WebSocketOptions:
70 """Holds options for WebSocketServer."""
71
72 def __init__(self, host, port, data_dir):
73 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
74 self.server_host = host
75 self.port = port
76 self.websock_handlers = data_dir
77 self.scan_dir = None
78 self.allow_handlers_outside_root_dir = False
79 self.websock_handlers_map_file = None
80 self.cgi_directories = []
81 self.is_executable_method = None
82 self.allow_draft75 = False
83 self.strict = True
84
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000085 self.use_tls = False
86 self.private_key = None
87 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000088 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000089 self.tls_client_ca = None
90 self.use_basic_auth = False
91
mattm@chromium.org830a3712012-11-07 23:00:07 +000092
agl@chromium.orgf9e66792011-12-12 22:22:19 +000093class RecordingSSLSessionCache(object):
94 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
95 lookups and inserts in order to test session cache behaviours."""
96
97 def __init__(self):
98 self.log = []
99
100 def __getitem__(self, sessionID):
101 self.log.append(('lookup', sessionID))
102 raise KeyError()
103
104 def __setitem__(self, sessionID, session):
105 self.log.append(('insert', sessionID))
106
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000107
108class ClientRestrictingServerMixIn:
109 """Implements verify_request to limit connections to our configured IP
110 address."""
111
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000112 def verify_request(self, _request, client_address):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000113 return client_address[0] == self.server_address[0]
114
115
initial.commit94958cf2008-07-26 22:42:52 +0000116class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000117 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000118 to be exited cleanly (by setting its "stop" member to True)."""
119
120 def serve_forever(self):
121 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000122 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000123 while not self.stop:
124 self.handle_request()
125 self.socket.close()
126
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127
128class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000129 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000130 verification."""
131
132 pass
133
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000134class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
135 """This is a specialization of HTTPServer that serves an
136 OCSP response"""
137
138 def serve_forever_on_thread(self):
139 self.thread = threading.Thread(target = self.serve_forever,
140 name = "OCSPServerThread")
141 self.thread.start()
142
143 def stop_serving(self):
144 self.shutdown()
145 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000146
mattm@chromium.org830a3712012-11-07 23:00:07 +0000147
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
149 ClientRestrictingServerMixIn,
150 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000151 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000152 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000153
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000154 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000155 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000156 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000157 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
158 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000159 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000160 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000161 self.tls_intolerant = tls_intolerant
162
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000163 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000164 s = open(ca_file).read()
165 x509 = tlslite.api.X509()
166 x509.parse(s)
167 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000168 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
169 if ssl_bulk_ciphers is not None:
170 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000171
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000172 if record_resume_info:
173 # If record_resume_info is true then we'll replace the session cache with
174 # an object that records the lookups and inserts that it sees.
175 self.session_cache = RecordingSSLSessionCache()
176 else:
177 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000178 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
179
180 def handshake(self, tlsConnection):
181 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000182
initial.commit94958cf2008-07-26 22:42:52 +0000183 try:
184 tlsConnection.handshakeServer(certChain=self.cert_chain,
185 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000186 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000187 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000188 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000189 reqCAs=self.ssl_client_cas,
190 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000191 tlsConnection.ignoreAbruptClose = True
192 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000193 except tlslite.api.TLSAbruptCloseError:
194 # Ignore abrupt close.
195 return True
initial.commit94958cf2008-07-26 22:42:52 +0000196 except tlslite.api.TLSError, error:
197 print "Handshake failure:", str(error)
198 return False
199
akalin@chromium.org154bb132010-11-12 02:20:27 +0000200
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000201class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000202 """An HTTP server that handles sync commands."""
203
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000204 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000205 # We import here to avoid pulling in chromiumsync's dependencies
206 # unless strictly necessary.
207 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000208 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000209 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000210 self._sync_handler = chromiumsync.TestServer()
211 self._xmpp_socket_map = {}
212 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000213 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000214 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000215 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000216
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000217 def GetXmppServer(self):
218 return self._xmpp_server
219
akalin@chromium.org154bb132010-11-12 02:20:27 +0000220 def HandleCommand(self, query, raw_request):
221 return self._sync_handler.HandleCommand(query, raw_request)
222
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000223 def HandleRequestNoBlock(self):
224 """Handles a single request.
225
226 Copied from SocketServer._handle_request_noblock().
227 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000228
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000229 try:
230 request, client_address = self.get_request()
231 except socket.error:
232 return
233 if self.verify_request(request, client_address):
234 try:
235 self.process_request(request, client_address)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000236 except Exception:
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000237 self.handle_error(request, client_address)
238 self.close_request(request)
239
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000240 def SetAuthenticated(self, auth_valid):
241 self.authenticated = auth_valid
242
243 def GetAuthenticated(self):
244 return self.authenticated
245
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000246 def serve_forever(self):
247 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
248 """
249
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000250 def HandleXmppSocket(fd, socket_map, handler):
251 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000252
253 Adapted from asyncore.read() et al.
254 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000255
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000256 xmpp_connection = socket_map.get(fd)
257 # This could happen if a previous handler call caused fd to get
258 # removed from socket_map.
259 if xmpp_connection is None:
260 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000261 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000262 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000263 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
264 raise
265 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000266 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000267
268 while True:
269 read_fds = [ self.fileno() ]
270 write_fds = []
271 exceptional_fds = []
272
273 for fd, xmpp_connection in self._xmpp_socket_map.items():
274 is_r = xmpp_connection.readable()
275 is_w = xmpp_connection.writable()
276 if is_r:
277 read_fds.append(fd)
278 if is_w:
279 write_fds.append(fd)
280 if is_r or is_w:
281 exceptional_fds.append(fd)
282
283 try:
284 read_fds, write_fds, exceptional_fds = (
285 select.select(read_fds, write_fds, exceptional_fds))
286 except select.error, err:
287 if err.args[0] != errno.EINTR:
288 raise
289 else:
290 continue
291
292 for fd in read_fds:
293 if fd == self.fileno():
294 self.HandleRequestNoBlock()
295 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000296 HandleXmppSocket(fd, self._xmpp_socket_map,
297 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000298
299 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000300 HandleXmppSocket(fd, self._xmpp_socket_map,
301 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000302
303 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000304 HandleXmppSocket(fd, self._xmpp_socket_map,
305 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000306
akalin@chromium.org154bb132010-11-12 02:20:27 +0000307
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000308class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
309 """This is a specialization of FTPServer that adds client verification."""
310
311 pass
312
313
314class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000315 """A TCP echo server that echoes back what it has received."""
316
317 def server_bind(self):
318 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000319
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000320 SocketServer.TCPServer.server_bind(self)
321 host, port = self.socket.getsockname()[:2]
322 self.server_name = socket.getfqdn(host)
323 self.server_port = port
324
325 def serve_forever(self):
326 self.stop = False
327 self.nonce_time = None
328 while not self.stop:
329 self.handle_request()
330 self.socket.close()
331
332
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000333class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000334 """A UDP echo server that echoes back what it has received."""
335
336 def server_bind(self):
337 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000338
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000339 SocketServer.UDPServer.server_bind(self)
340 host, port = self.socket.getsockname()[:2]
341 self.server_name = socket.getfqdn(host)
342 self.server_port = port
343
344 def serve_forever(self):
345 self.stop = False
346 self.nonce_time = None
347 while not self.stop:
348 self.handle_request()
349 self.socket.close()
350
351
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
353
354 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000355 connect_handlers, get_handlers, head_handlers, post_handlers,
356 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000357 self._connect_handlers = connect_handlers
358 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000359 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000360 self._post_handlers = post_handlers
361 self._put_handlers = put_handlers
362 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
363 self, request, client_address, socket_server)
364
365 def log_request(self, *args, **kwargs):
366 # Disable request logging to declutter test log output.
367 pass
368
369 def _ShouldHandleRequest(self, handler_name):
370 """Determines if the path can be handled by the handler.
371
372 We consider a handler valid if the path begins with the
373 handler name. It can optionally be followed by "?*", "/*".
374 """
375
376 pattern = re.compile('%s($|\?|/).*' % handler_name)
377 return pattern.match(self.path)
378
379 def do_CONNECT(self):
380 for handler in self._connect_handlers:
381 if handler():
382 return
383
384 def do_GET(self):
385 for handler in self._get_handlers:
386 if handler():
387 return
388
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000389 def do_HEAD(self):
390 for handler in self._head_handlers:
391 if handler():
392 return
393
akalin@chromium.org154bb132010-11-12 02:20:27 +0000394 def do_POST(self):
395 for handler in self._post_handlers:
396 if handler():
397 return
398
399 def do_PUT(self):
400 for handler in self._put_handlers:
401 if handler():
402 return
403
404
405class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000406
407 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000408 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000409 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000410 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000411 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000412 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.NoCacheMaxAgeTimeHandler,
414 self.NoCacheTimeHandler,
415 self.CacheTimeHandler,
416 self.CacheExpiresHandler,
417 self.CacheProxyRevalidateHandler,
418 self.CachePrivateHandler,
419 self.CachePublicHandler,
420 self.CacheSMaxAgeHandler,
421 self.CacheMustRevalidateHandler,
422 self.CacheMustRevalidateMaxAgeHandler,
423 self.CacheNoStoreHandler,
424 self.CacheNoStoreMaxAgeHandler,
425 self.CacheNoTransformHandler,
426 self.DownloadHandler,
427 self.DownloadFinishHandler,
428 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000429 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000430 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000431 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000432 self.GDataAuthHandler,
433 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000434 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000435 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000436 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000437 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000438 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000439 self.AuthBasicHandler,
440 self.AuthDigestHandler,
441 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000442 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000444 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000445 self.ServerRedirectHandler,
446 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000447 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000448 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000449 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000450 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000451 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000452 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000453 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000454 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000455 self.DeviceManagementHandler,
456 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000457 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000458 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000459 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000460 head_handlers = [
461 self.FileHandler,
462 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000463
maruel@google.come250a9b2009-03-10 17:39:46 +0000464 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000465 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000466 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000467 'gif': 'image/gif',
468 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000469 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000470 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000471 'pdf' : 'application/pdf',
472 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000473 }
initial.commit94958cf2008-07-26 22:42:52 +0000474 self._default_mime_type = 'text/html'
475
akalin@chromium.org154bb132010-11-12 02:20:27 +0000476 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000477 connect_handlers, get_handlers, head_handlers,
478 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479
initial.commit94958cf2008-07-26 22:42:52 +0000480 def GetMIMETypeFromName(self, file_name):
481 """Returns the mime type for the specified file_name. So far it only looks
482 at the file extension."""
483
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000484 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000485 if len(extension) == 0:
486 # no extension.
487 return self._default_mime_type
488
ericroman@google.comc17ca532009-05-07 03:51:05 +0000489 # extension starts with a dot, so we need to remove it
490 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000491
initial.commit94958cf2008-07-26 22:42:52 +0000492 def NoCacheMaxAgeTimeHandler(self):
493 """This request handler yields a page with the title set to the current
494 system time, and no caching requested."""
495
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000496 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000497 return False
498
499 self.send_response(200)
500 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000501 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000502 self.end_headers()
503
maruel@google.come250a9b2009-03-10 17:39:46 +0000504 self.wfile.write('<html><head><title>%s</title></head></html>' %
505 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000506
507 return True
508
509 def NoCacheTimeHandler(self):
510 """This request handler yields a page with the title set to the current
511 system time, and no caching requested."""
512
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000513 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000514 return False
515
516 self.send_response(200)
517 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000518 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000519 self.end_headers()
520
maruel@google.come250a9b2009-03-10 17:39:46 +0000521 self.wfile.write('<html><head><title>%s</title></head></html>' %
522 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000523
524 return True
525
526 def CacheTimeHandler(self):
527 """This request handler yields a page with the title set to the current
528 system time, and allows caching for one minute."""
529
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000530 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000531 return False
532
533 self.send_response(200)
534 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000535 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000536 self.end_headers()
537
maruel@google.come250a9b2009-03-10 17:39:46 +0000538 self.wfile.write('<html><head><title>%s</title></head></html>' %
539 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000540
541 return True
542
543 def CacheExpiresHandler(self):
544 """This request handler yields a page with the title set to the current
545 system time, and set the page to expire on 1 Jan 2099."""
546
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000547 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000548 return False
549
550 self.send_response(200)
551 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000552 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000553 self.end_headers()
554
maruel@google.come250a9b2009-03-10 17:39:46 +0000555 self.wfile.write('<html><head><title>%s</title></head></html>' %
556 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000557
558 return True
559
560 def CacheProxyRevalidateHandler(self):
561 """This request handler yields a page with the title set to the current
562 system time, and allows caching for 60 seconds"""
563
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000564 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000565 return False
566
567 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000568 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000569 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
570 self.end_headers()
571
maruel@google.come250a9b2009-03-10 17:39:46 +0000572 self.wfile.write('<html><head><title>%s</title></head></html>' %
573 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000574
575 return True
576
577 def CachePrivateHandler(self):
578 """This request handler yields a page with the title set to the current
579 system time, and allows caching for 5 seconds."""
580
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000581 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000582 return False
583
584 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000585 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000586 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000587 self.end_headers()
588
maruel@google.come250a9b2009-03-10 17:39:46 +0000589 self.wfile.write('<html><head><title>%s</title></head></html>' %
590 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000591
592 return True
593
594 def CachePublicHandler(self):
595 """This request handler yields a page with the title set to the current
596 system time, and allows caching for 5 seconds."""
597
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000598 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000599 return False
600
601 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000602 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000603 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000604 self.end_headers()
605
maruel@google.come250a9b2009-03-10 17:39:46 +0000606 self.wfile.write('<html><head><title>%s</title></head></html>' %
607 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000608
609 return True
610
611 def CacheSMaxAgeHandler(self):
612 """This request handler yields a page with the title set to the current
613 system time, and does not allow for caching."""
614
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000615 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000616 return False
617
618 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000619 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000620 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
621 self.end_headers()
622
maruel@google.come250a9b2009-03-10 17:39:46 +0000623 self.wfile.write('<html><head><title>%s</title></head></html>' %
624 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000625
626 return True
627
628 def CacheMustRevalidateHandler(self):
629 """This request handler yields a page with the title set to the current
630 system time, and does not allow caching."""
631
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000632 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000633 return False
634
635 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000636 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000637 self.send_header('Cache-Control', 'must-revalidate')
638 self.end_headers()
639
maruel@google.come250a9b2009-03-10 17:39:46 +0000640 self.wfile.write('<html><head><title>%s</title></head></html>' %
641 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000642
643 return True
644
645 def CacheMustRevalidateMaxAgeHandler(self):
646 """This request handler yields a page with the title set to the current
647 system time, and does not allow caching event though max-age of 60
648 seconds is specified."""
649
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000650 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000651 return False
652
653 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000654 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000655 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
656 self.end_headers()
657
maruel@google.come250a9b2009-03-10 17:39:46 +0000658 self.wfile.write('<html><head><title>%s</title></head></html>' %
659 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000660
661 return True
662
initial.commit94958cf2008-07-26 22:42:52 +0000663 def CacheNoStoreHandler(self):
664 """This request handler yields a page with the title set to the current
665 system time, and does not allow the page to be stored."""
666
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000667 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000668 return False
669
670 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000671 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000672 self.send_header('Cache-Control', 'no-store')
673 self.end_headers()
674
maruel@google.come250a9b2009-03-10 17:39:46 +0000675 self.wfile.write('<html><head><title>%s</title></head></html>' %
676 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000677
678 return True
679
680 def CacheNoStoreMaxAgeHandler(self):
681 """This request handler yields a page with the title set to the current
682 system time, and does not allow the page to be stored even though max-age
683 of 60 seconds is specified."""
684
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000685 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000686 return False
687
688 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000689 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000690 self.send_header('Cache-Control', 'max-age=60, no-store')
691 self.end_headers()
692
maruel@google.come250a9b2009-03-10 17:39:46 +0000693 self.wfile.write('<html><head><title>%s</title></head></html>' %
694 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000695
696 return True
697
698
699 def CacheNoTransformHandler(self):
700 """This request handler yields a page with the title set to the current
701 system time, and does not allow the content to transformed during
702 user-agent caching"""
703
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000704 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000705 return False
706
707 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000708 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000709 self.send_header('Cache-Control', 'no-transform')
710 self.end_headers()
711
maruel@google.come250a9b2009-03-10 17:39:46 +0000712 self.wfile.write('<html><head><title>%s</title></head></html>' %
713 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000714
715 return True
716
717 def EchoHeader(self):
718 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000719
ananta@chromium.org219b2062009-10-23 16:09:41 +0000720 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000721
ananta@chromium.org56812d02011-04-07 17:52:05 +0000722 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000723 """This function echoes back the value of a specific request header while
724 allowing caching for 16 hours."""
725
ananta@chromium.org56812d02011-04-07 17:52:05 +0000726 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000727
728 def EchoHeaderHelper(self, echo_header):
729 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000730
ananta@chromium.org219b2062009-10-23 16:09:41 +0000731 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000732 return False
733
734 query_char = self.path.find('?')
735 if query_char != -1:
736 header_name = self.path[query_char+1:]
737
738 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000739 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000740 if echo_header == '/echoheadercache':
741 self.send_header('Cache-control', 'max-age=60000')
742 else:
743 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000744 # insert a vary header to properly indicate that the cachability of this
745 # request is subject to value of the request header being echoed.
746 if len(header_name) > 0:
747 self.send_header('Vary', header_name)
748 self.end_headers()
749
750 if len(header_name) > 0:
751 self.wfile.write(self.headers.getheader(header_name))
752
753 return True
754
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000755 def ReadRequestBody(self):
756 """This function reads the body of the current HTTP request, handling
757 both plain and chunked transfer encoded requests."""
758
759 if self.headers.getheader('transfer-encoding') != 'chunked':
760 length = int(self.headers.getheader('content-length'))
761 return self.rfile.read(length)
762
763 # Read the request body as chunks.
764 body = ""
765 while True:
766 line = self.rfile.readline()
767 length = int(line, 16)
768 if length == 0:
769 self.rfile.readline()
770 break
771 body += self.rfile.read(length)
772 self.rfile.read(2)
773 return body
774
initial.commit94958cf2008-07-26 22:42:52 +0000775 def EchoHandler(self):
776 """This handler just echoes back the payload of the request, for testing
777 form submission."""
778
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000779 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000780 return False
781
782 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000783 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000784 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000785 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000786 return True
787
788 def EchoTitleHandler(self):
789 """This handler is like Echo, but sets the page title to the request."""
790
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000791 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000792 return False
793
794 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000795 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000796 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000797 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000798 self.wfile.write('<html><head><title>')
799 self.wfile.write(request)
800 self.wfile.write('</title></head></html>')
801 return True
802
803 def EchoAllHandler(self):
804 """This handler yields a (more) human-readable page listing information
805 about the request header & contents."""
806
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000807 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000808 return False
809
810 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000811 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000812 self.end_headers()
813 self.wfile.write('<html><head><style>'
814 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
815 '</style></head><body>'
816 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000817 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000818 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000819
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000820 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000821 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000822 params = cgi.parse_qs(qs, keep_blank_values=1)
823
824 for param in params:
825 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000826
827 self.wfile.write('</pre>')
828
829 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
830
831 self.wfile.write('</body></html>')
832 return True
833
834 def DownloadHandler(self):
835 """This handler sends a downloadable file with or without reporting
836 the size (6K)."""
837
838 if self.path.startswith("/download-unknown-size"):
839 send_length = False
840 elif self.path.startswith("/download-known-size"):
841 send_length = True
842 else:
843 return False
844
845 #
846 # The test which uses this functionality is attempting to send
847 # small chunks of data to the client. Use a fairly large buffer
848 # so that we'll fill chrome's IO buffer enough to force it to
849 # actually write the data.
850 # See also the comments in the client-side of this test in
851 # download_uitest.cc
852 #
853 size_chunk1 = 35*1024
854 size_chunk2 = 10*1024
855
856 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000857 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000858 self.send_header('Cache-Control', 'max-age=0')
859 if send_length:
860 self.send_header('Content-Length', size_chunk1 + size_chunk2)
861 self.end_headers()
862
863 # First chunk of data:
864 self.wfile.write("*" * size_chunk1)
865 self.wfile.flush()
866
867 # handle requests until one of them clears this flag.
868 self.server.waitForDownload = True
869 while self.server.waitForDownload:
870 self.server.handle_request()
871
872 # Second chunk of data:
873 self.wfile.write("*" * size_chunk2)
874 return True
875
876 def DownloadFinishHandler(self):
877 """This handler just tells the server to finish the current download."""
878
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000879 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000880 return False
881
882 self.server.waitForDownload = False
883 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000884 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000885 self.send_header('Cache-Control', 'max-age=0')
886 self.end_headers()
887 return True
888
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000889 def _ReplaceFileData(self, data, query_parameters):
890 """Replaces matching substrings in a file.
891
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000892 If the 'replace_text' URL query parameter is present, it is expected to be
893 of the form old_text:new_text, which indicates that any old_text strings in
894 the file are replaced with new_text. Multiple 'replace_text' parameters may
895 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000896
897 If the parameters are not present, |data| is returned.
898 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000899
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000900 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000901 replace_text_values = query_dict.get('replace_text', [])
902 for replace_text_value in replace_text_values:
903 replace_text_args = replace_text_value.split(':')
904 if len(replace_text_args) != 2:
905 raise ValueError(
906 'replace_text must be of form old_text:new_text. Actual value: %s' %
907 replace_text_value)
908 old_text_b64, new_text_b64 = replace_text_args
909 old_text = base64.urlsafe_b64decode(old_text_b64)
910 new_text = base64.urlsafe_b64decode(new_text_b64)
911 data = data.replace(old_text, new_text)
912 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000913
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000914 def ZipFileHandler(self):
915 """This handler sends the contents of the requested file in compressed form.
916 Can pass in a parameter that specifies that the content length be
917 C - the compressed size (OK),
918 U - the uncompressed size (Non-standard, but handled),
919 S - less than compressed (OK because we keep going),
920 M - larger than compressed but less than uncompressed (an error),
921 L - larger than uncompressed (an error)
922 Example: compressedfiles/Picture_1.doc?C
923 """
924
925 prefix = "/compressedfiles/"
926 if not self.path.startswith(prefix):
927 return False
928
929 # Consume a request body if present.
930 if self.command == 'POST' or self.command == 'PUT' :
931 self.ReadRequestBody()
932
933 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
934
935 if not query in ('C', 'U', 'S', 'M', 'L'):
936 return False
937
938 sub_path = url_path[len(prefix):]
939 entries = sub_path.split('/')
940 file_path = os.path.join(self.server.data_dir, *entries)
941 if os.path.isdir(file_path):
942 file_path = os.path.join(file_path, 'index.html')
943
944 if not os.path.isfile(file_path):
945 print "File not found " + sub_path + " full path:" + file_path
946 self.send_error(404)
947 return True
948
949 f = open(file_path, "rb")
950 data = f.read()
951 uncompressed_len = len(data)
952 f.close()
953
954 # Compress the data.
955 data = zlib.compress(data)
956 compressed_len = len(data)
957
958 content_length = compressed_len
959 if query == 'U':
960 content_length = uncompressed_len
961 elif query == 'S':
962 content_length = compressed_len / 2
963 elif query == 'M':
964 content_length = (compressed_len + uncompressed_len) / 2
965 elif query == 'L':
966 content_length = compressed_len + uncompressed_len
967
968 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000969 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000970 self.send_header('Content-encoding', 'deflate')
971 self.send_header('Connection', 'close')
972 self.send_header('Content-Length', content_length)
973 self.send_header('ETag', '\'' + file_path + '\'')
974 self.end_headers()
975
976 self.wfile.write(data)
977
978 return True
979
initial.commit94958cf2008-07-26 22:42:52 +0000980 def FileHandler(self):
981 """This handler sends the contents of the requested file. Wow, it's like
982 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000983
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000984 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000985 if not self.path.startswith(prefix):
986 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000987 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000988
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000989 def PostOnlyFileHandler(self):
990 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000991
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000992 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000993 if not self.path.startswith(prefix):
994 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000995 return self._FileHandlerHelper(prefix)
996
997 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000998 request_body = ''
999 if self.command == 'POST' or self.command == 'PUT':
1000 # Consume a request body if present.
1001 request_body = self.ReadRequestBody()
1002
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001003 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001004 query_dict = cgi.parse_qs(query)
1005
1006 expected_body = query_dict.get('expected_body', [])
1007 if expected_body and request_body not in expected_body:
1008 self.send_response(404)
1009 self.end_headers()
1010 self.wfile.write('')
1011 return True
1012
1013 expected_headers = query_dict.get('expected_headers', [])
1014 for expected_header in expected_headers:
1015 header_name, expected_value = expected_header.split(':')
1016 if self.headers.getheader(header_name) != expected_value:
1017 self.send_response(404)
1018 self.end_headers()
1019 self.wfile.write('')
1020 return True
1021
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001022 sub_path = url_path[len(prefix):]
1023 entries = sub_path.split('/')
1024 file_path = os.path.join(self.server.data_dir, *entries)
1025 if os.path.isdir(file_path):
1026 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001027
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001028 if not os.path.isfile(file_path):
1029 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001030 self.send_error(404)
1031 return True
1032
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001033 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001034 data = f.read()
1035 f.close()
1036
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001037 data = self._ReplaceFileData(data, query)
1038
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001039 old_protocol_version = self.protocol_version
1040
initial.commit94958cf2008-07-26 22:42:52 +00001041 # If file.mock-http-headers exists, it contains the headers we
1042 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001043 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001044 if os.path.isfile(headers_path):
1045 f = open(headers_path, "r")
1046
1047 # "HTTP/1.1 200 OK"
1048 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001049 http_major, http_minor, status_code = re.findall(
1050 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1051 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001052 self.send_response(int(status_code))
1053
1054 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001055 header_values = re.findall('(\S+):\s*(.*)', line)
1056 if len(header_values) > 0:
1057 # "name: value"
1058 name, value = header_values[0]
1059 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001060 f.close()
1061 else:
1062 # Could be more generic once we support mime-type sniffing, but for
1063 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001064
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001065 range_header = self.headers.get('Range')
1066 if range_header and range_header.startswith('bytes='):
1067 # Note this doesn't handle all valid byte range_header values (i.e.
1068 # left open ended ones), just enough for what we needed so far.
1069 range_header = range_header[6:].split('-')
1070 start = int(range_header[0])
1071 if range_header[1]:
1072 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001073 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001074 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001075
1076 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001077 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1078 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001079 self.send_header('Content-Range', content_range)
1080 data = data[start: end + 1]
1081 else:
1082 self.send_response(200)
1083
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001084 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001085 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001086 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001087 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001088 self.end_headers()
1089
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001090 if (self.command != 'HEAD'):
1091 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001092
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001093 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001094 return True
1095
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001096 def SetCookieHandler(self):
1097 """This handler just sets a cookie, for testing cookie handling."""
1098
1099 if not self._ShouldHandleRequest("/set-cookie"):
1100 return False
1101
1102 query_char = self.path.find('?')
1103 if query_char != -1:
1104 cookie_values = self.path[query_char + 1:].split('&')
1105 else:
1106 cookie_values = ("",)
1107 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001108 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001109 for cookie_value in cookie_values:
1110 self.send_header('Set-Cookie', '%s' % cookie_value)
1111 self.end_headers()
1112 for cookie_value in cookie_values:
1113 self.wfile.write('%s' % cookie_value)
1114 return True
1115
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001116 def SetManyCookiesHandler(self):
1117 """This handler just sets a given number of cookies, for testing handling
1118 of large numbers of cookies."""
1119
1120 if not self._ShouldHandleRequest("/set-many-cookies"):
1121 return False
1122
1123 query_char = self.path.find('?')
1124 if query_char != -1:
1125 num_cookies = int(self.path[query_char + 1:])
1126 else:
1127 num_cookies = 0
1128 self.send_response(200)
1129 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001130 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001131 self.send_header('Set-Cookie', 'a=')
1132 self.end_headers()
1133 self.wfile.write('%d cookies were sent' % num_cookies)
1134 return True
1135
mattm@chromium.org983fc462012-06-30 00:52:08 +00001136 def ExpectAndSetCookieHandler(self):
1137 """Expects some cookies to be sent, and if they are, sets more cookies.
1138
1139 The expect parameter specifies a required cookie. May be specified multiple
1140 times.
1141 The set parameter specifies a cookie to set if all required cookies are
1142 preset. May be specified multiple times.
1143 The data parameter specifies the response body data to be returned."""
1144
1145 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1146 return False
1147
1148 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1149 query_dict = cgi.parse_qs(query)
1150 cookies = set()
1151 if 'Cookie' in self.headers:
1152 cookie_header = self.headers.getheader('Cookie')
1153 cookies.update([s.strip() for s in cookie_header.split(';')])
1154 got_all_expected_cookies = True
1155 for expected_cookie in query_dict.get('expect', []):
1156 if expected_cookie not in cookies:
1157 got_all_expected_cookies = False
1158 self.send_response(200)
1159 self.send_header('Content-Type', 'text/html')
1160 if got_all_expected_cookies:
1161 for cookie_value in query_dict.get('set', []):
1162 self.send_header('Set-Cookie', '%s' % cookie_value)
1163 self.end_headers()
1164 for data_value in query_dict.get('data', []):
1165 self.wfile.write(data_value)
1166 return True
1167
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001168 def SetHeaderHandler(self):
1169 """This handler sets a response header. Parameters are in the
1170 key%3A%20value&key2%3A%20value2 format."""
1171
1172 if not self._ShouldHandleRequest("/set-header"):
1173 return False
1174
1175 query_char = self.path.find('?')
1176 if query_char != -1:
1177 headers_values = self.path[query_char + 1:].split('&')
1178 else:
1179 headers_values = ("",)
1180 self.send_response(200)
1181 self.send_header('Content-Type', 'text/html')
1182 for header_value in headers_values:
1183 header_value = urllib.unquote(header_value)
1184 (key, value) = header_value.split(': ', 1)
1185 self.send_header(key, value)
1186 self.end_headers()
1187 for header_value in headers_values:
1188 self.wfile.write('%s' % header_value)
1189 return True
1190
initial.commit94958cf2008-07-26 22:42:52 +00001191 def AuthBasicHandler(self):
1192 """This handler tests 'Basic' authentication. It just sends a page with
1193 title 'user/pass' if you succeed."""
1194
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001195 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001196 return False
1197
1198 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001199 expected_password = 'secret'
1200 realm = 'testrealm'
1201 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001202
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001203 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1204 query_params = cgi.parse_qs(query, True)
1205 if 'set-cookie-if-challenged' in query_params:
1206 set_cookie_if_challenged = True
1207 if 'password' in query_params:
1208 expected_password = query_params['password'][0]
1209 if 'realm' in query_params:
1210 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001211
initial.commit94958cf2008-07-26 22:42:52 +00001212 auth = self.headers.getheader('authorization')
1213 try:
1214 if not auth:
1215 raise Exception('no auth')
1216 b64str = re.findall(r'Basic (\S+)', auth)[0]
1217 userpass = base64.b64decode(b64str)
1218 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001219 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001220 raise Exception('wrong password')
1221 except Exception, e:
1222 # Authentication failed.
1223 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001224 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001225 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001226 if set_cookie_if_challenged:
1227 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001228 self.end_headers()
1229 self.wfile.write('<html><head>')
1230 self.wfile.write('<title>Denied: %s</title>' % e)
1231 self.wfile.write('</head><body>')
1232 self.wfile.write('auth=%s<p>' % auth)
1233 self.wfile.write('b64str=%s<p>' % b64str)
1234 self.wfile.write('username: %s<p>' % username)
1235 self.wfile.write('userpass: %s<p>' % userpass)
1236 self.wfile.write('password: %s<p>' % password)
1237 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1238 self.wfile.write('</body></html>')
1239 return True
1240
1241 # Authentication successful. (Return a cachable response to allow for
1242 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001243 old_protocol_version = self.protocol_version
1244 self.protocol_version = "HTTP/1.1"
1245
initial.commit94958cf2008-07-26 22:42:52 +00001246 if_none_match = self.headers.getheader('if-none-match')
1247 if if_none_match == "abc":
1248 self.send_response(304)
1249 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001250 elif url_path.endswith(".gif"):
1251 # Using chrome/test/data/google/logo.gif as the test image
1252 test_image_path = ['google', 'logo.gif']
1253 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1254 if not os.path.isfile(gif_path):
1255 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001256 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001257 return True
1258
1259 f = open(gif_path, "rb")
1260 data = f.read()
1261 f.close()
1262
1263 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001264 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001265 self.send_header('Cache-control', 'max-age=60000')
1266 self.send_header('Etag', 'abc')
1267 self.end_headers()
1268 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001269 else:
1270 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001271 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001272 self.send_header('Cache-control', 'max-age=60000')
1273 self.send_header('Etag', 'abc')
1274 self.end_headers()
1275 self.wfile.write('<html><head>')
1276 self.wfile.write('<title>%s/%s</title>' % (username, password))
1277 self.wfile.write('</head><body>')
1278 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001279 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001280 self.wfile.write('</body></html>')
1281
rvargas@google.com54453b72011-05-19 01:11:11 +00001282 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001283 return True
1284
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001285 def GDataAuthHandler(self):
1286 """This handler verifies the Authentication header for GData requests."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001287
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001288 if not self.server.gdata_auth_token:
1289 # --auth-token is not specified, not the test case for GData.
1290 return False
1291
1292 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1293 return False
1294
1295 if 'GData-Version' not in self.headers:
1296 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1297 return True
1298
1299 if 'Authorization' not in self.headers:
1300 self.send_error(httplib.UNAUTHORIZED)
1301 return True
1302
1303 field_prefix = 'Bearer '
1304 authorization = self.headers['Authorization']
1305 if not authorization.startswith(field_prefix):
1306 self.send_error(httplib.UNAUTHORIZED)
1307 return True
1308
1309 code = authorization[len(field_prefix):]
1310 if code != self.server.gdata_auth_token:
1311 self.send_error(httplib.UNAUTHORIZED)
1312 return True
1313
1314 return False
1315
1316 def GDataDocumentsFeedQueryHandler(self):
1317 """This handler verifies if required parameters are properly
1318 specified for the GData DocumentsFeed request."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001319
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001320 if not self.server.gdata_auth_token:
1321 # --auth-token is not specified, not the test case for GData.
1322 return False
1323
1324 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1325 return False
1326
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001327 (_path, _question, query_params) = self.path.partition('?')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001328 self.query_params = urlparse.parse_qs(query_params)
1329
1330 if 'v' not in self.query_params:
1331 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1332 return True
1333 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1334 # currently our GData client only uses JSON format.
1335 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1336 return True
1337
1338 return False
1339
tonyg@chromium.org75054202010-03-31 22:06:10 +00001340 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001341 """Returns a nonce that's stable per request path for the server's lifetime.
1342 This is a fake implementation. A real implementation would only use a given
1343 nonce a single time (hence the name n-once). However, for the purposes of
1344 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001345
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001346 Args:
1347 force_reset: Iff set, the nonce will be changed. Useful for testing the
1348 "stale" response.
1349 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001350
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001351 if force_reset or not self.server.nonce_time:
1352 self.server.nonce_time = time.time()
1353 return hashlib.md5('privatekey%s%d' %
1354 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001355
1356 def AuthDigestHandler(self):
1357 """This handler tests 'Digest' authentication.
1358
1359 It just sends a page with title 'user/pass' if you succeed.
1360
1361 A stale response is sent iff "stale" is present in the request path.
1362 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001363
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001364 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001365 return False
1366
tonyg@chromium.org75054202010-03-31 22:06:10 +00001367 stale = 'stale' in self.path
1368 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001369 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001370 password = 'secret'
1371 realm = 'testrealm'
1372
1373 auth = self.headers.getheader('authorization')
1374 pairs = {}
1375 try:
1376 if not auth:
1377 raise Exception('no auth')
1378 if not auth.startswith('Digest'):
1379 raise Exception('not digest')
1380 # Pull out all the name="value" pairs as a dictionary.
1381 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1382
1383 # Make sure it's all valid.
1384 if pairs['nonce'] != nonce:
1385 raise Exception('wrong nonce')
1386 if pairs['opaque'] != opaque:
1387 raise Exception('wrong opaque')
1388
1389 # Check the 'response' value and make sure it matches our magic hash.
1390 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001391 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001392 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001393 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001394 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001395 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001396 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1397 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001398 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001399
1400 if pairs['response'] != response:
1401 raise Exception('wrong password')
1402 except Exception, e:
1403 # Authentication failed.
1404 self.send_response(401)
1405 hdr = ('Digest '
1406 'realm="%s", '
1407 'domain="/", '
1408 'qop="auth", '
1409 'algorithm=MD5, '
1410 'nonce="%s", '
1411 'opaque="%s"') % (realm, nonce, opaque)
1412 if stale:
1413 hdr += ', stale="TRUE"'
1414 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001416 self.end_headers()
1417 self.wfile.write('<html><head>')
1418 self.wfile.write('<title>Denied: %s</title>' % e)
1419 self.wfile.write('</head><body>')
1420 self.wfile.write('auth=%s<p>' % auth)
1421 self.wfile.write('pairs=%s<p>' % pairs)
1422 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1423 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1424 self.wfile.write('</body></html>')
1425 return True
1426
1427 # Authentication successful.
1428 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001429 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001430 self.end_headers()
1431 self.wfile.write('<html><head>')
1432 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1433 self.wfile.write('</head><body>')
1434 self.wfile.write('auth=%s<p>' % auth)
1435 self.wfile.write('pairs=%s<p>' % pairs)
1436 self.wfile.write('</body></html>')
1437
1438 return True
1439
1440 def SlowServerHandler(self):
1441 """Wait for the user suggested time before responding. The syntax is
1442 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001443
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001444 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001445 return False
1446 query_char = self.path.find('?')
1447 wait_sec = 1.0
1448 if query_char >= 0:
1449 try:
1450 wait_sec = int(self.path[query_char + 1:])
1451 except ValueError:
1452 pass
1453 time.sleep(wait_sec)
1454 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001455 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001456 self.end_headers()
1457 self.wfile.write("waited %d seconds" % wait_sec)
1458 return True
1459
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001460 def ChunkedServerHandler(self):
1461 """Send chunked response. Allows to specify chunks parameters:
1462 - waitBeforeHeaders - ms to wait before sending headers
1463 - waitBetweenChunks - ms to wait between chunks
1464 - chunkSize - size of each chunk in bytes
1465 - chunksNumber - number of chunks
1466 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1467 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001468
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001469 if not self._ShouldHandleRequest("/chunked"):
1470 return False
1471 query_char = self.path.find('?')
1472 chunkedSettings = {'waitBeforeHeaders' : 0,
1473 'waitBetweenChunks' : 0,
1474 'chunkSize' : 5,
1475 'chunksNumber' : 5}
1476 if query_char >= 0:
1477 params = self.path[query_char + 1:].split('&')
1478 for param in params:
1479 keyValue = param.split('=')
1480 if len(keyValue) == 2:
1481 try:
1482 chunkedSettings[keyValue[0]] = int(keyValue[1])
1483 except ValueError:
1484 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001485 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001486 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1487 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001488 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001489 self.send_header('Connection', 'close')
1490 self.send_header('Transfer-Encoding', 'chunked')
1491 self.end_headers()
1492 # Chunked encoding: sending all chunks, then final zero-length chunk and
1493 # then final CRLF.
1494 for i in range(0, chunkedSettings['chunksNumber']):
1495 if i > 0:
1496 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1497 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001498 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001499 self.sendChunkHelp('')
1500 return True
1501
initial.commit94958cf2008-07-26 22:42:52 +00001502 def ContentTypeHandler(self):
1503 """Returns a string of html with the given content type. E.g.,
1504 /contenttype?text/css returns an html file with the Content-Type
1505 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001507 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001508 return False
1509 query_char = self.path.find('?')
1510 content_type = self.path[query_char + 1:].strip()
1511 if not content_type:
1512 content_type = 'text/html'
1513 self.send_response(200)
1514 self.send_header('Content-Type', content_type)
1515 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001516 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001517 return True
1518
creis@google.com2f4f6a42011-03-25 19:44:19 +00001519 def NoContentHandler(self):
1520 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001521
creis@google.com2f4f6a42011-03-25 19:44:19 +00001522 if not self._ShouldHandleRequest("/nocontent"):
1523 return False
1524 self.send_response(204)
1525 self.end_headers()
1526 return True
1527
initial.commit94958cf2008-07-26 22:42:52 +00001528 def ServerRedirectHandler(self):
1529 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001530 '/server-redirect?http://foo.bar/asdf' to redirect to
1531 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001532
1533 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001534 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001535 return False
1536
1537 query_char = self.path.find('?')
1538 if query_char < 0 or len(self.path) <= query_char + 1:
1539 self.sendRedirectHelp(test_name)
1540 return True
1541 dest = self.path[query_char + 1:]
1542
1543 self.send_response(301) # moved permanently
1544 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001545 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001546 self.end_headers()
1547 self.wfile.write('<html><head>')
1548 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1549
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001550 return True
initial.commit94958cf2008-07-26 22:42:52 +00001551
1552 def ClientRedirectHandler(self):
1553 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001554 '/client-redirect?http://foo.bar/asdf' to redirect to
1555 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001556
1557 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001558 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001559 return False
1560
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001561 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001562 if query_char < 0 or len(self.path) <= query_char + 1:
1563 self.sendRedirectHelp(test_name)
1564 return True
1565 dest = self.path[query_char + 1:]
1566
1567 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001568 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001569 self.end_headers()
1570 self.wfile.write('<html><head>')
1571 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1572 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1573
1574 return True
1575
tony@chromium.org03266982010-03-05 03:18:42 +00001576 def MultipartHandler(self):
1577 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001578
tony@chromium.org4cb88302011-09-27 22:13:49 +00001579 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001580 if not self._ShouldHandleRequest(test_name):
1581 return False
1582
1583 num_frames = 10
1584 bound = '12345'
1585 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001586 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001587 'multipart/x-mixed-replace;boundary=' + bound)
1588 self.end_headers()
1589
1590 for i in xrange(num_frames):
1591 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001592 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001593 self.wfile.write('<title>page ' + str(i) + '</title>')
1594 self.wfile.write('page ' + str(i))
1595
1596 self.wfile.write('--' + bound + '--')
1597 return True
1598
tony@chromium.org4cb88302011-09-27 22:13:49 +00001599 def MultipartSlowHandler(self):
1600 """Send a multipart response (3 text/html pages) with a slight delay
1601 between each page. This is similar to how some pages show status using
1602 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001603
tony@chromium.org4cb88302011-09-27 22:13:49 +00001604 test_name = '/multipart-slow'
1605 if not self._ShouldHandleRequest(test_name):
1606 return False
1607
1608 num_frames = 3
1609 bound = '12345'
1610 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001611 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001612 'multipart/x-mixed-replace;boundary=' + bound)
1613 self.end_headers()
1614
1615 for i in xrange(num_frames):
1616 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001617 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001618 time.sleep(0.25)
1619 if i == 2:
1620 self.wfile.write('<title>PASS</title>')
1621 else:
1622 self.wfile.write('<title>page ' + str(i) + '</title>')
1623 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1624
1625 self.wfile.write('--' + bound + '--')
1626 return True
1627
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001628 def GetSSLSessionCacheHandler(self):
1629 """Send a reply containing a log of the session cache operations."""
1630
1631 if not self._ShouldHandleRequest('/ssl-session-cache'):
1632 return False
1633
1634 self.send_response(200)
1635 self.send_header('Content-Type', 'text/plain')
1636 self.end_headers()
1637 try:
1638 for (action, sessionID) in self.server.session_cache.log:
1639 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001640 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001641 self.wfile.write('Pass --https-record-resume in order to use' +
1642 ' this request')
1643 return True
1644
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001645 def CloseSocketHandler(self):
1646 """Closes the socket without sending anything."""
1647
1648 if not self._ShouldHandleRequest('/close-socket'):
1649 return False
1650
1651 self.wfile.close()
1652 return True
1653
initial.commit94958cf2008-07-26 22:42:52 +00001654 def DefaultResponseHandler(self):
1655 """This is the catch-all response handler for requests that aren't handled
1656 by one of the special handlers above.
1657 Note that we specify the content-length as without it the https connection
1658 is not closed properly (and the browser keeps expecting data)."""
1659
1660 contents = "Default response given for path: " + self.path
1661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 self.send_header('Content-Type', 'text/html')
1663 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001664 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001665 if (self.command != 'HEAD'):
1666 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001667 return True
1668
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001669 def RedirectConnectHandler(self):
1670 """Sends a redirect to the CONNECT request for www.redirect.com. This
1671 response is not specified by the RFC, so the browser should not follow
1672 the redirect."""
1673
1674 if (self.path.find("www.redirect.com") < 0):
1675 return False
1676
1677 dest = "http://www.destination.com/foo.js"
1678
1679 self.send_response(302) # moved temporarily
1680 self.send_header('Location', dest)
1681 self.send_header('Connection', 'close')
1682 self.end_headers()
1683 return True
1684
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001685 def ServerAuthConnectHandler(self):
1686 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1687 response doesn't make sense because the proxy server cannot request
1688 server authentication."""
1689
1690 if (self.path.find("www.server-auth.com") < 0):
1691 return False
1692
1693 challenge = 'Basic realm="WallyWorld"'
1694
1695 self.send_response(401) # unauthorized
1696 self.send_header('WWW-Authenticate', challenge)
1697 self.send_header('Connection', 'close')
1698 self.end_headers()
1699 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001700
1701 def DefaultConnectResponseHandler(self):
1702 """This is the catch-all response handler for CONNECT requests that aren't
1703 handled by one of the special handlers above. Real Web servers respond
1704 with 400 to CONNECT requests."""
1705
1706 contents = "Your client has issued a malformed or illegal request."
1707 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001708 self.send_header('Content-Type', 'text/html')
1709 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001710 self.end_headers()
1711 self.wfile.write(contents)
1712 return True
1713
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001714 def DeviceManagementHandler(self):
1715 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001716
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001717 if not self._ShouldHandleRequest("/device_management"):
1718 return False
1719
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001720 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001721
1722 if not self.server._device_management_handler:
1723 import device_management
1724 policy_path = os.path.join(self.server.data_dir, 'device_management')
1725 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001726 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001727 self.server.policy_keys,
1728 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001729
1730 http_response, raw_reply = (
1731 self.server._device_management_handler.HandleRequest(self.path,
1732 self.headers,
1733 raw_request))
1734 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001735 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001736 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001737 self.end_headers()
1738 self.wfile.write(raw_reply)
1739 return True
1740
initial.commit94958cf2008-07-26 22:42:52 +00001741 # called by the redirect handling function when there is no parameter
1742 def sendRedirectHelp(self, redirect_name):
1743 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001744 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001745 self.end_headers()
1746 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1747 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1748 self.wfile.write('</body></html>')
1749
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001750 # called by chunked handling function
1751 def sendChunkHelp(self, chunk):
1752 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1753 self.wfile.write('%X\r\n' % len(chunk))
1754 self.wfile.write(chunk)
1755 self.wfile.write('\r\n')
1756
akalin@chromium.org154bb132010-11-12 02:20:27 +00001757
1758class SyncPageHandler(BasePageHandler):
1759 """Handler for the main HTTP sync server."""
1760
1761 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001762 get_handlers = [self.ChromiumSyncTimeHandler,
1763 self.ChromiumSyncMigrationOpHandler,
1764 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001765 self.ChromiumSyncDisableNotificationsOpHandler,
1766 self.ChromiumSyncEnableNotificationsOpHandler,
1767 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001768 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001769 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001770 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001771 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001772 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001773
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001774 post_handlers = [self.ChromiumSyncCommandHandler,
1775 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001776 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001777 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001778 post_handlers, [])
1779
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001780
akalin@chromium.org154bb132010-11-12 02:20:27 +00001781 def ChromiumSyncTimeHandler(self):
1782 """Handle Chromium sync .../time requests.
1783
1784 The syncer sometimes checks server reachability by examining /time.
1785 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001786
akalin@chromium.org154bb132010-11-12 02:20:27 +00001787 test_name = "/chromiumsync/time"
1788 if not self._ShouldHandleRequest(test_name):
1789 return False
1790
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001791 # Chrome hates it if we send a response before reading the request.
1792 if self.headers.getheader('content-length'):
1793 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001794 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001795
akalin@chromium.org154bb132010-11-12 02:20:27 +00001796 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001797 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001798 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001799 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001800 return True
1801
1802 def ChromiumSyncCommandHandler(self):
1803 """Handle a chromiumsync command arriving via http.
1804
1805 This covers all sync protocol commands: authentication, getupdates, and
1806 commit.
1807 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001808
akalin@chromium.org154bb132010-11-12 02:20:27 +00001809 test_name = "/chromiumsync/command"
1810 if not self._ShouldHandleRequest(test_name):
1811 return False
1812
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001813 length = int(self.headers.getheader('content-length'))
1814 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001815 http_response = 200
1816 raw_reply = None
1817 if not self.server.GetAuthenticated():
1818 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001819 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1820 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001821 else:
1822 http_response, raw_reply = self.server.HandleCommand(
1823 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001824
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001825 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001826 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001827 if http_response == 401:
1828 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001829 self.end_headers()
1830 self.wfile.write(raw_reply)
1831 return True
1832
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001833 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001834 test_name = "/chromiumsync/migrate"
1835 if not self._ShouldHandleRequest(test_name):
1836 return False
1837
1838 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1839 self.path)
1840 self.send_response(http_response)
1841 self.send_header('Content-Type', 'text/html')
1842 self.send_header('Content-Length', len(raw_reply))
1843 self.end_headers()
1844 self.wfile.write(raw_reply)
1845 return True
1846
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001847 def ChromiumSyncCredHandler(self):
1848 test_name = "/chromiumsync/cred"
1849 if not self._ShouldHandleRequest(test_name):
1850 return False
1851 try:
1852 query = urlparse.urlparse(self.path)[4]
1853 cred_valid = urlparse.parse_qs(query)['valid']
1854 if cred_valid[0] == 'True':
1855 self.server.SetAuthenticated(True)
1856 else:
1857 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001858 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001859 self.server.SetAuthenticated(False)
1860
1861 http_response = 200
1862 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1863 self.send_response(http_response)
1864 self.send_header('Content-Type', 'text/html')
1865 self.send_header('Content-Length', len(raw_reply))
1866 self.end_headers()
1867 self.wfile.write(raw_reply)
1868 return True
1869
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001870 def ChromiumSyncDisableNotificationsOpHandler(self):
1871 test_name = "/chromiumsync/disablenotifications"
1872 if not self._ShouldHandleRequest(test_name):
1873 return False
1874 self.server.GetXmppServer().DisableNotifications()
1875 result = 200
1876 raw_reply = ('<html><title>Notifications disabled</title>'
1877 '<H1>Notifications disabled</H1></html>')
1878 self.send_response(result)
1879 self.send_header('Content-Type', 'text/html')
1880 self.send_header('Content-Length', len(raw_reply))
1881 self.end_headers()
1882 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001883 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001884
1885 def ChromiumSyncEnableNotificationsOpHandler(self):
1886 test_name = "/chromiumsync/enablenotifications"
1887 if not self._ShouldHandleRequest(test_name):
1888 return False
1889 self.server.GetXmppServer().EnableNotifications()
1890 result = 200
1891 raw_reply = ('<html><title>Notifications enabled</title>'
1892 '<H1>Notifications enabled</H1></html>')
1893 self.send_response(result)
1894 self.send_header('Content-Type', 'text/html')
1895 self.send_header('Content-Length', len(raw_reply))
1896 self.end_headers()
1897 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001898 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001899
1900 def ChromiumSyncSendNotificationOpHandler(self):
1901 test_name = "/chromiumsync/sendnotification"
1902 if not self._ShouldHandleRequest(test_name):
1903 return False
1904 query = urlparse.urlparse(self.path)[4]
1905 query_params = urlparse.parse_qs(query)
1906 channel = ''
1907 data = ''
1908 if 'channel' in query_params:
1909 channel = query_params['channel'][0]
1910 if 'data' in query_params:
1911 data = query_params['data'][0]
1912 self.server.GetXmppServer().SendNotification(channel, data)
1913 result = 200
1914 raw_reply = ('<html><title>Notification sent</title>'
1915 '<H1>Notification sent with channel "%s" '
1916 'and data "%s"</H1></html>'
1917 % (channel, data))
1918 self.send_response(result)
1919 self.send_header('Content-Type', 'text/html')
1920 self.send_header('Content-Length', len(raw_reply))
1921 self.end_headers()
1922 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001923 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001924
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001925 def ChromiumSyncBirthdayErrorOpHandler(self):
1926 test_name = "/chromiumsync/birthdayerror"
1927 if not self._ShouldHandleRequest(test_name):
1928 return False
1929 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1930 self.send_response(result)
1931 self.send_header('Content-Type', 'text/html')
1932 self.send_header('Content-Length', len(raw_reply))
1933 self.end_headers()
1934 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001935 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001936
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001937 def ChromiumSyncTransientErrorOpHandler(self):
1938 test_name = "/chromiumsync/transienterror"
1939 if not self._ShouldHandleRequest(test_name):
1940 return False
1941 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1942 self.send_response(result)
1943 self.send_header('Content-Type', 'text/html')
1944 self.send_header('Content-Length', len(raw_reply))
1945 self.end_headers()
1946 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001947 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001948
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001949 def ChromiumSyncErrorOpHandler(self):
1950 test_name = "/chromiumsync/error"
1951 if not self._ShouldHandleRequest(test_name):
1952 return False
1953 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1954 self.path)
1955 self.send_response(result)
1956 self.send_header('Content-Type', 'text/html')
1957 self.send_header('Content-Length', len(raw_reply))
1958 self.end_headers()
1959 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001960 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001961
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001962 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1963 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001964 if not self._ShouldHandleRequest(test_name):
1965 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001966 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001967 self.send_response(result)
1968 self.send_header('Content-Type', 'text/html')
1969 self.send_header('Content-Length', len(raw_reply))
1970 self.end_headers()
1971 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001972 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001973
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001974 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1975 test_name = "/chromiumsync/createsyncedbookmarks"
1976 if not self._ShouldHandleRequest(test_name):
1977 return False
1978 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1979 self.send_response(result)
1980 self.send_header('Content-Type', 'text/html')
1981 self.send_header('Content-Length', len(raw_reply))
1982 self.end_headers()
1983 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001984 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001985
akalin@chromium.org154bb132010-11-12 02:20:27 +00001986
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001987class OCSPHandler(BasePageHandler):
1988 def __init__(self, request, client_address, socket_server):
1989 handlers = [self.OCSPResponse]
1990 self.ocsp_response = socket_server.ocsp_response
1991 BasePageHandler.__init__(self, request, client_address, socket_server,
1992 [], handlers, [], handlers, [])
1993
1994 def OCSPResponse(self):
1995 self.send_response(200)
1996 self.send_header('Content-Type', 'application/ocsp-response')
1997 self.send_header('Content-Length', str(len(self.ocsp_response)))
1998 self.end_headers()
1999
2000 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002001
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002003class TCPEchoHandler(SocketServer.BaseRequestHandler):
2004 """The RequestHandler class for TCP echo server.
2005
2006 It is instantiated once per connection to the server, and overrides the
2007 handle() method to implement communication to the client.
2008 """
2009
2010 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002011 """Handles the request from the client and constructs a response."""
2012
2013 data = self.request.recv(65536).strip()
2014 # Verify the "echo request" message received from the client. Send back
2015 # "echo response" message if "echo request" message is valid.
2016 try:
2017 return_data = echo_message.GetEchoResponseData(data)
2018 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002019 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002020 except ValueError:
2021 return
2022
2023 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002024
2025
2026class UDPEchoHandler(SocketServer.BaseRequestHandler):
2027 """The RequestHandler class for UDP echo server.
2028
2029 It is instantiated once per connection to the server, and overrides the
2030 handle() method to implement communication to the client.
2031 """
2032
2033 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002034 """Handles the request from the client and constructs a response."""
2035
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002036 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002037 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002038 # Verify the "echo request" message received from the client. Send back
2039 # "echo response" message if "echo request" message is valid.
2040 try:
2041 return_data = echo_message.GetEchoResponseData(data)
2042 if not return_data:
2043 return
2044 except ValueError:
2045 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002046 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002047
2048
bashi@chromium.org33233532012-09-08 17:37:24 +00002049class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2050 """A request handler that behaves as a proxy server which requires
2051 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2052 """
2053
2054 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2055
2056 def parse_request(self):
2057 """Overrides parse_request to check credential."""
2058
2059 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2060 return False
2061
2062 auth = self.headers.getheader('Proxy-Authorization')
2063 if auth != self._AUTH_CREDENTIAL:
2064 self.send_response(407)
2065 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2066 self.end_headers()
2067 return False
2068
2069 return True
2070
2071 def _start_read_write(self, sock):
2072 sock.setblocking(0)
2073 self.request.setblocking(0)
2074 rlist = [self.request, sock]
2075 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002076 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002077 if errors:
2078 self.send_response(500)
2079 self.end_headers()
2080 return
2081 for s in ready_sockets:
2082 received = s.recv(1024)
2083 if len(received) == 0:
2084 return
2085 if s == self.request:
2086 other = sock
2087 else:
2088 other = self.request
2089 other.send(received)
2090
2091 def _do_common_method(self):
2092 url = urlparse.urlparse(self.path)
2093 port = url.port
2094 if not port:
2095 if url.scheme == 'http':
2096 port = 80
2097 elif url.scheme == 'https':
2098 port = 443
2099 if not url.hostname or not port:
2100 self.send_response(400)
2101 self.end_headers()
2102 return
2103
2104 if len(url.path) == 0:
2105 path = '/'
2106 else:
2107 path = url.path
2108 if len(url.query) > 0:
2109 path = '%s?%s' % (url.path, url.query)
2110
2111 sock = None
2112 try:
2113 sock = socket.create_connection((url.hostname, port))
2114 sock.send('%s %s %s\r\n' % (
2115 self.command, path, self.protocol_version))
2116 for header in self.headers.headers:
2117 header = header.strip()
2118 if (header.lower().startswith('connection') or
2119 header.lower().startswith('proxy')):
2120 continue
2121 sock.send('%s\r\n' % header)
2122 sock.send('\r\n')
2123 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002124 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002125 self.send_response(500)
2126 self.end_headers()
2127 finally:
2128 if sock is not None:
2129 sock.close()
2130
2131 def do_CONNECT(self):
2132 try:
2133 pos = self.path.rfind(':')
2134 host = self.path[:pos]
2135 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002136 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002137 self.send_response(400)
2138 self.end_headers()
2139
2140 try:
2141 sock = socket.create_connection((host, port))
2142 self.send_response(200, 'Connection established')
2143 self.end_headers()
2144 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002145 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002146 self.send_response(500)
2147 self.end_headers()
2148 finally:
2149 sock.close()
2150
2151 def do_GET(self):
2152 self._do_common_method()
2153
2154 def do_HEAD(self):
2155 self._do_common_method()
2156
2157
mattm@chromium.org830a3712012-11-07 23:00:07 +00002158class ServerRunner(testserver_base.TestServerRunner):
2159 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002160
mattm@chromium.org830a3712012-11-07 23:00:07 +00002161 def __init__(self):
2162 super(ServerRunner, self).__init__()
2163 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002164
mattm@chromium.org830a3712012-11-07 23:00:07 +00002165 def __make_data_dir(self):
2166 if self.options.data_dir:
2167 if not os.path.isdir(self.options.data_dir):
2168 raise testserver_base.OptionError('specified data dir not found: ' +
2169 self.options.data_dir + ' exiting...')
2170 my_data_dir = self.options.data_dir
2171 else:
2172 # Create the default path to our data dir, relative to the exe dir.
2173 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2174 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002175
mattm@chromium.org830a3712012-11-07 23:00:07 +00002176 #TODO(ibrar): Must use Find* funtion defined in google\tools
2177 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002178
mattm@chromium.org830a3712012-11-07 23:00:07 +00002179 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002180
mattm@chromium.org830a3712012-11-07 23:00:07 +00002181 def create_server(self, server_data):
2182 port = self.options.port
2183 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002184
mattm@chromium.org830a3712012-11-07 23:00:07 +00002185 if self.options.server_type == SERVER_HTTP:
2186 if self.options.https:
2187 pem_cert_and_key = None
2188 if self.options.cert_and_key_file:
2189 if not os.path.isfile(self.options.cert_and_key_file):
2190 raise testserver_base.OptionError(
2191 'specified server cert file not found: ' +
2192 self.options.cert_and_key_file + ' exiting...')
2193 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002194 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002195 # generate a new certificate and run an OCSP server for it.
2196 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2197 print ('OCSP server started on %s:%d...' %
2198 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002199
mattm@chromium.org830a3712012-11-07 23:00:07 +00002200 ocsp_der = None
2201 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002202
mattm@chromium.org830a3712012-11-07 23:00:07 +00002203 if self.options.ocsp == 'ok':
2204 ocsp_state = minica.OCSP_STATE_GOOD
2205 elif self.options.ocsp == 'revoked':
2206 ocsp_state = minica.OCSP_STATE_REVOKED
2207 elif self.options.ocsp == 'invalid':
2208 ocsp_state = minica.OCSP_STATE_INVALID
2209 elif self.options.ocsp == 'unauthorized':
2210 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2211 elif self.options.ocsp == 'unknown':
2212 ocsp_state = minica.OCSP_STATE_UNKNOWN
2213 else:
2214 raise testserver_base.OptionError('unknown OCSP status: ' +
2215 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002216
mattm@chromium.org830a3712012-11-07 23:00:07 +00002217 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2218 subject = "127.0.0.1",
2219 ocsp_url = ("http://%s:%d/ocsp" %
2220 (host, self.__ocsp_server.server_port)),
2221 ocsp_state = ocsp_state)
2222
2223 self.__ocsp_server.ocsp_response = ocsp_der
2224
2225 for ca_cert in self.options.ssl_client_ca:
2226 if not os.path.isfile(ca_cert):
2227 raise testserver_base.OptionError(
2228 'specified trusted client CA file not found: ' + ca_cert +
2229 ' exiting...')
2230 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2231 self.options.ssl_client_auth,
2232 self.options.ssl_client_ca,
2233 self.options.ssl_bulk_cipher,
2234 self.options.record_resume,
2235 self.options.tls_intolerant)
2236 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2237 else:
2238 server = HTTPServer((host, port), TestPageHandler)
2239 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2240
2241 server.data_dir = self.__make_data_dir()
2242 server.file_root_url = self.options.file_root_url
2243 server_data['port'] = server.server_port
2244 server._device_management_handler = None
2245 server.policy_keys = self.options.policy_keys
2246 server.policy_user = self.options.policy_user
2247 server.gdata_auth_token = self.options.auth_token
2248 elif self.options.server_type == SERVER_WEBSOCKET:
2249 # Launch pywebsocket via WebSocketServer.
2250 logger = logging.getLogger()
2251 logger.addHandler(logging.StreamHandler())
2252 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2253 # is required to work correctly. It should be fixed from pywebsocket side.
2254 os.chdir(self.__make_data_dir())
2255 websocket_options = WebSocketOptions(host, port, '.')
2256 if self.options.cert_and_key_file:
2257 websocket_options.use_tls = True
2258 websocket_options.private_key = self.options.cert_and_key_file
2259 websocket_options.certificate = self.options.cert_and_key_file
2260 if self.options.ssl_client_auth:
2261 websocket_options.tls_client_auth = True
2262 if len(self.options.ssl_client_ca) != 1:
2263 raise testserver_base.OptionError(
2264 'one trusted client CA file should be specified')
2265 if not os.path.isfile(self.options.ssl_client_ca[0]):
2266 raise testserver_base.OptionError(
2267 'specified trusted client CA file not found: ' +
2268 self.options.ssl_client_ca[0] + ' exiting...')
2269 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2270 server = WebSocketServer(websocket_options)
2271 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2272 server_data['port'] = server.server_port
2273 elif self.options.server_type == SERVER_SYNC:
2274 xmpp_port = self.options.xmpp_port
2275 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2276 print 'Sync HTTP server started on port %d...' % server.server_port
2277 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2278 server_data['port'] = server.server_port
2279 server_data['xmpp_port'] = server.xmpp_port
2280 elif self.options.server_type == SERVER_TCP_ECHO:
2281 # Used for generating the key (randomly) that encodes the "echo request"
2282 # message.
2283 random.seed()
2284 server = TCPEchoServer((host, port), TCPEchoHandler)
2285 print 'Echo TCP server started on port %d...' % server.server_port
2286 server_data['port'] = server.server_port
2287 elif self.options.server_type == SERVER_UDP_ECHO:
2288 # Used for generating the key (randomly) that encodes the "echo request"
2289 # message.
2290 random.seed()
2291 server = UDPEchoServer((host, port), UDPEchoHandler)
2292 print 'Echo UDP server started on port %d...' % server.server_port
2293 server_data['port'] = server.server_port
2294 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2295 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2296 print 'BasicAuthProxy server started on port %d...' % server.server_port
2297 server_data['port'] = server.server_port
2298 elif self.options.server_type == SERVER_FTP:
2299 my_data_dir = self.__make_data_dir()
2300
2301 # Instantiate a dummy authorizer for managing 'virtual' users
2302 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2303
2304 # Define a new user having full r/w permissions and a read-only
2305 # anonymous user
2306 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2307
2308 authorizer.add_anonymous(my_data_dir)
2309
2310 # Instantiate FTP handler class
2311 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2312 ftp_handler.authorizer = authorizer
2313
2314 # Define a customized banner (string returned when client connects)
2315 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2316 pyftpdlib.ftpserver.__ver__)
2317
2318 # Instantiate FTP server class and listen to address:port
2319 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2320 server_data['port'] = server.socket.getsockname()[1]
2321 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002322 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002323 raise testserver_base.OptionError('unknown server type' +
2324 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002325
mattm@chromium.org830a3712012-11-07 23:00:07 +00002326 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002327
mattm@chromium.org830a3712012-11-07 23:00:07 +00002328 def run_server(self):
2329 if self.__ocsp_server:
2330 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002331
mattm@chromium.org830a3712012-11-07 23:00:07 +00002332 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002333
mattm@chromium.org830a3712012-11-07 23:00:07 +00002334 if self.__ocsp_server:
2335 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002336
mattm@chromium.org830a3712012-11-07 23:00:07 +00002337 def add_options(self):
2338 testserver_base.TestServerRunner.add_options(self)
2339 self.option_parser.add_option('-f', '--ftp', action='store_const',
2340 const=SERVER_FTP, default=SERVER_HTTP,
2341 dest='server_type',
2342 help='start up an FTP server.')
2343 self.option_parser.add_option('--sync', action='store_const',
2344 const=SERVER_SYNC, default=SERVER_HTTP,
2345 dest='server_type',
2346 help='start up a sync server.')
2347 self.option_parser.add_option('--tcp-echo', action='store_const',
2348 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2349 dest='server_type',
2350 help='start up a tcp echo server.')
2351 self.option_parser.add_option('--udp-echo', action='store_const',
2352 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2353 dest='server_type',
2354 help='start up a udp echo server.')
2355 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2356 const=SERVER_BASIC_AUTH_PROXY,
2357 default=SERVER_HTTP, dest='server_type',
2358 help='start up a proxy server which requires '
2359 'basic authentication.')
2360 self.option_parser.add_option('--websocket', action='store_const',
2361 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2362 dest='server_type',
2363 help='start up a WebSocket server.')
2364 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2365 help='Port used by the XMPP server. If '
2366 'unspecified, the XMPP server will listen on '
2367 'an ephemeral port.')
2368 self.option_parser.add_option('--data-dir', dest='data_dir',
2369 help='Directory from which to read the '
2370 'files.')
2371 self.option_parser.add_option('--https', action='store_true',
2372 dest='https', help='Specify that https '
2373 'should be used.')
2374 self.option_parser.add_option('--cert-and-key-file',
2375 dest='cert_and_key_file', help='specify the '
2376 'path to the file containing the certificate '
2377 'and private key for the server in PEM '
2378 'format')
2379 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2380 help='The type of OCSP response generated '
2381 'for the automatically generated '
2382 'certificate. One of [ok,revoked,invalid]')
2383 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2384 default='0', type='int',
2385 help='If nonzero, certain TLS connections '
2386 'will be aborted in order to test version '
2387 'fallback. 1 means all TLS versions will be '
2388 'aborted. 2 means TLS 1.1 or higher will be '
2389 'aborted. 3 means TLS 1.2 or higher will be '
2390 'aborted.')
2391 self.option_parser.add_option('--https-record-resume',
2392 dest='record_resume', const=True,
2393 default=False, action='store_const',
2394 help='Record resumption cache events rather '
2395 'than resuming as normal. Allows the use of '
2396 'the /ssl-session-cache request')
2397 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2398 help='Require SSL client auth on every '
2399 'connection.')
2400 self.option_parser.add_option('--ssl-client-ca', action='append',
2401 default=[], help='Specify that the client '
2402 'certificate request should include the CA '
2403 'named in the subject of the DER-encoded '
2404 'certificate contained in the specified '
2405 'file. This option may appear multiple '
2406 'times, indicating multiple CA names should '
2407 'be sent in the request.')
2408 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2409 help='Specify the bulk encryption '
2410 'algorithm(s) that will be accepted by the '
2411 'SSL server. Valid values are "aes256", '
2412 '"aes128", "3des", "rc4". If omitted, all '
2413 'algorithms will be used. This option may '
2414 'appear multiple times, indicating '
2415 'multiple algorithms should be enabled.');
2416 self.option_parser.add_option('--file-root-url', default='/files/',
2417 help='Specify a root URL for files served.')
2418 self.option_parser.add_option('--policy-key', action='append',
2419 dest='policy_keys',
2420 help='Specify a path to a PEM-encoded '
2421 'private key to use for policy signing. May '
2422 'be specified multiple times in order to '
2423 'load multipe keys into the server. If the '
2424 'server has multiple keys, it will rotate '
2425 'through them in at each request a '
2426 'round-robin fashion. The server will '
2427 'generate a random key if none is specified '
2428 'on the command line.')
2429 self.option_parser.add_option('--policy-user',
2430 default='user@example.com',
2431 dest='policy_user',
2432 help='Specify the user name the server '
2433 'should report back to the client as the '
2434 'user owning the token used for making the '
2435 'policy request.')
2436 self.option_parser.add_option('--auth-token', dest='auth_token',
2437 help='Specify the auth token which should be '
2438 'used in the authorization header for GData.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002439
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002440
initial.commit94958cf2008-07-26 22:42:52 +00002441if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002442 sys.exit(ServerRunner().main())