blob: 9c5468df88cc24fec554b7b513679d5d6bd14f64 [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 = (
joaodasilva@chromium.org3c069da2012-11-20 16:17:15 +00001726 device_management.TestServer(policy_path, self.server.policy_keys))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001727
1728 http_response, raw_reply = (
1729 self.server._device_management_handler.HandleRequest(self.path,
1730 self.headers,
1731 raw_request))
1732 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001733 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001734 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001735 self.end_headers()
1736 self.wfile.write(raw_reply)
1737 return True
1738
initial.commit94958cf2008-07-26 22:42:52 +00001739 # called by the redirect handling function when there is no parameter
1740 def sendRedirectHelp(self, redirect_name):
1741 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001742 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001743 self.end_headers()
1744 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1745 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1746 self.wfile.write('</body></html>')
1747
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001748 # called by chunked handling function
1749 def sendChunkHelp(self, chunk):
1750 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1751 self.wfile.write('%X\r\n' % len(chunk))
1752 self.wfile.write(chunk)
1753 self.wfile.write('\r\n')
1754
akalin@chromium.org154bb132010-11-12 02:20:27 +00001755
1756class SyncPageHandler(BasePageHandler):
1757 """Handler for the main HTTP sync server."""
1758
1759 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001760 get_handlers = [self.ChromiumSyncTimeHandler,
1761 self.ChromiumSyncMigrationOpHandler,
1762 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001763 self.ChromiumSyncDisableNotificationsOpHandler,
1764 self.ChromiumSyncEnableNotificationsOpHandler,
1765 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001766 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001767 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001768 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001769 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001770 self.ChromiumSyncCreateSyncedBookmarksOpHandler,
1771 self.ChromiumSyncEnableKeystoreEncryptionOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001772
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001773 post_handlers = [self.ChromiumSyncCommandHandler,
1774 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001775 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001776 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001777 post_handlers, [])
1778
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001779
akalin@chromium.org154bb132010-11-12 02:20:27 +00001780 def ChromiumSyncTimeHandler(self):
1781 """Handle Chromium sync .../time requests.
1782
1783 The syncer sometimes checks server reachability by examining /time.
1784 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001785
akalin@chromium.org154bb132010-11-12 02:20:27 +00001786 test_name = "/chromiumsync/time"
1787 if not self._ShouldHandleRequest(test_name):
1788 return False
1789
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001790 # Chrome hates it if we send a response before reading the request.
1791 if self.headers.getheader('content-length'):
1792 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001793 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001794
akalin@chromium.org154bb132010-11-12 02:20:27 +00001795 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001796 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001797 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001798 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001799 return True
1800
1801 def ChromiumSyncCommandHandler(self):
1802 """Handle a chromiumsync command arriving via http.
1803
1804 This covers all sync protocol commands: authentication, getupdates, and
1805 commit.
1806 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001807
akalin@chromium.org154bb132010-11-12 02:20:27 +00001808 test_name = "/chromiumsync/command"
1809 if not self._ShouldHandleRequest(test_name):
1810 return False
1811
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001812 length = int(self.headers.getheader('content-length'))
1813 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001814 http_response = 200
1815 raw_reply = None
1816 if not self.server.GetAuthenticated():
1817 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001818 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1819 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001820 else:
1821 http_response, raw_reply = self.server.HandleCommand(
1822 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001823
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001824 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001825 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001826 if http_response == 401:
1827 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001828 self.end_headers()
1829 self.wfile.write(raw_reply)
1830 return True
1831
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001832 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001833 test_name = "/chromiumsync/migrate"
1834 if not self._ShouldHandleRequest(test_name):
1835 return False
1836
1837 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1838 self.path)
1839 self.send_response(http_response)
1840 self.send_header('Content-Type', 'text/html')
1841 self.send_header('Content-Length', len(raw_reply))
1842 self.end_headers()
1843 self.wfile.write(raw_reply)
1844 return True
1845
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001846 def ChromiumSyncCredHandler(self):
1847 test_name = "/chromiumsync/cred"
1848 if not self._ShouldHandleRequest(test_name):
1849 return False
1850 try:
1851 query = urlparse.urlparse(self.path)[4]
1852 cred_valid = urlparse.parse_qs(query)['valid']
1853 if cred_valid[0] == 'True':
1854 self.server.SetAuthenticated(True)
1855 else:
1856 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001857 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001858 self.server.SetAuthenticated(False)
1859
1860 http_response = 200
1861 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1862 self.send_response(http_response)
1863 self.send_header('Content-Type', 'text/html')
1864 self.send_header('Content-Length', len(raw_reply))
1865 self.end_headers()
1866 self.wfile.write(raw_reply)
1867 return True
1868
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001869 def ChromiumSyncDisableNotificationsOpHandler(self):
1870 test_name = "/chromiumsync/disablenotifications"
1871 if not self._ShouldHandleRequest(test_name):
1872 return False
1873 self.server.GetXmppServer().DisableNotifications()
1874 result = 200
1875 raw_reply = ('<html><title>Notifications disabled</title>'
1876 '<H1>Notifications disabled</H1></html>')
1877 self.send_response(result)
1878 self.send_header('Content-Type', 'text/html')
1879 self.send_header('Content-Length', len(raw_reply))
1880 self.end_headers()
1881 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001882 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001883
1884 def ChromiumSyncEnableNotificationsOpHandler(self):
1885 test_name = "/chromiumsync/enablenotifications"
1886 if not self._ShouldHandleRequest(test_name):
1887 return False
1888 self.server.GetXmppServer().EnableNotifications()
1889 result = 200
1890 raw_reply = ('<html><title>Notifications enabled</title>'
1891 '<H1>Notifications enabled</H1></html>')
1892 self.send_response(result)
1893 self.send_header('Content-Type', 'text/html')
1894 self.send_header('Content-Length', len(raw_reply))
1895 self.end_headers()
1896 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001897 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001898
1899 def ChromiumSyncSendNotificationOpHandler(self):
1900 test_name = "/chromiumsync/sendnotification"
1901 if not self._ShouldHandleRequest(test_name):
1902 return False
1903 query = urlparse.urlparse(self.path)[4]
1904 query_params = urlparse.parse_qs(query)
1905 channel = ''
1906 data = ''
1907 if 'channel' in query_params:
1908 channel = query_params['channel'][0]
1909 if 'data' in query_params:
1910 data = query_params['data'][0]
1911 self.server.GetXmppServer().SendNotification(channel, data)
1912 result = 200
1913 raw_reply = ('<html><title>Notification sent</title>'
1914 '<H1>Notification sent with channel "%s" '
1915 'and data "%s"</H1></html>'
1916 % (channel, data))
1917 self.send_response(result)
1918 self.send_header('Content-Type', 'text/html')
1919 self.send_header('Content-Length', len(raw_reply))
1920 self.end_headers()
1921 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001922 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001923
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001924 def ChromiumSyncBirthdayErrorOpHandler(self):
1925 test_name = "/chromiumsync/birthdayerror"
1926 if not self._ShouldHandleRequest(test_name):
1927 return False
1928 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1929 self.send_response(result)
1930 self.send_header('Content-Type', 'text/html')
1931 self.send_header('Content-Length', len(raw_reply))
1932 self.end_headers()
1933 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001934 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001935
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001936 def ChromiumSyncTransientErrorOpHandler(self):
1937 test_name = "/chromiumsync/transienterror"
1938 if not self._ShouldHandleRequest(test_name):
1939 return False
1940 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1941 self.send_response(result)
1942 self.send_header('Content-Type', 'text/html')
1943 self.send_header('Content-Length', len(raw_reply))
1944 self.end_headers()
1945 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001946 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001947
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001948 def ChromiumSyncErrorOpHandler(self):
1949 test_name = "/chromiumsync/error"
1950 if not self._ShouldHandleRequest(test_name):
1951 return False
1952 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1953 self.path)
1954 self.send_response(result)
1955 self.send_header('Content-Type', 'text/html')
1956 self.send_header('Content-Length', len(raw_reply))
1957 self.end_headers()
1958 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001959 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001960
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001961 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1962 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001963 if not self._ShouldHandleRequest(test_name):
1964 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001965 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001966 self.send_response(result)
1967 self.send_header('Content-Type', 'text/html')
1968 self.send_header('Content-Length', len(raw_reply))
1969 self.end_headers()
1970 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001971 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001972
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001973 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1974 test_name = "/chromiumsync/createsyncedbookmarks"
1975 if not self._ShouldHandleRequest(test_name):
1976 return False
1977 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1978 self.send_response(result)
1979 self.send_header('Content-Type', 'text/html')
1980 self.send_header('Content-Length', len(raw_reply))
1981 self.end_headers()
1982 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001983 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001984
zea@chromium.orgbb26c702012-11-15 02:43:40 +00001985 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
1986 test_name = "/chromiumsync/enablekeystoreencryption"
1987 if not self._ShouldHandleRequest(test_name):
1988 return False
1989 result, raw_reply = (
1990 self.server._sync_handler.HandleEnableKeystoreEncryption())
1991 self.send_response(result)
1992 self.send_header('Content-Type', 'text/html')
1993 self.send_header('Content-Length', len(raw_reply))
1994 self.end_headers()
1995 self.wfile.write(raw_reply)
1996 return True
1997
akalin@chromium.org154bb132010-11-12 02:20:27 +00001998
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001999class OCSPHandler(BasePageHandler):
2000 def __init__(self, request, client_address, socket_server):
2001 handlers = [self.OCSPResponse]
2002 self.ocsp_response = socket_server.ocsp_response
2003 BasePageHandler.__init__(self, request, client_address, socket_server,
2004 [], handlers, [], handlers, [])
2005
2006 def OCSPResponse(self):
2007 self.send_response(200)
2008 self.send_header('Content-Type', 'application/ocsp-response')
2009 self.send_header('Content-Length', str(len(self.ocsp_response)))
2010 self.end_headers()
2011
2012 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002013
mattm@chromium.org830a3712012-11-07 23:00:07 +00002014
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002015class TCPEchoHandler(SocketServer.BaseRequestHandler):
2016 """The RequestHandler class for TCP echo server.
2017
2018 It is instantiated once per connection to the server, and overrides the
2019 handle() method to implement communication to the client.
2020 """
2021
2022 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002023 """Handles the request from the client and constructs a response."""
2024
2025 data = self.request.recv(65536).strip()
2026 # Verify the "echo request" message received from the client. Send back
2027 # "echo response" message if "echo request" message is valid.
2028 try:
2029 return_data = echo_message.GetEchoResponseData(data)
2030 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002031 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002032 except ValueError:
2033 return
2034
2035 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002036
2037
2038class UDPEchoHandler(SocketServer.BaseRequestHandler):
2039 """The RequestHandler class for UDP echo server.
2040
2041 It is instantiated once per connection to the server, and overrides the
2042 handle() method to implement communication to the client.
2043 """
2044
2045 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002046 """Handles the request from the client and constructs a response."""
2047
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002048 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002049 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002050 # Verify the "echo request" message received from the client. Send back
2051 # "echo response" message if "echo request" message is valid.
2052 try:
2053 return_data = echo_message.GetEchoResponseData(data)
2054 if not return_data:
2055 return
2056 except ValueError:
2057 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002058 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002059
2060
bashi@chromium.org33233532012-09-08 17:37:24 +00002061class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2062 """A request handler that behaves as a proxy server which requires
2063 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2064 """
2065
2066 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2067
2068 def parse_request(self):
2069 """Overrides parse_request to check credential."""
2070
2071 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2072 return False
2073
2074 auth = self.headers.getheader('Proxy-Authorization')
2075 if auth != self._AUTH_CREDENTIAL:
2076 self.send_response(407)
2077 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2078 self.end_headers()
2079 return False
2080
2081 return True
2082
2083 def _start_read_write(self, sock):
2084 sock.setblocking(0)
2085 self.request.setblocking(0)
2086 rlist = [self.request, sock]
2087 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002088 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002089 if errors:
2090 self.send_response(500)
2091 self.end_headers()
2092 return
2093 for s in ready_sockets:
2094 received = s.recv(1024)
2095 if len(received) == 0:
2096 return
2097 if s == self.request:
2098 other = sock
2099 else:
2100 other = self.request
2101 other.send(received)
2102
2103 def _do_common_method(self):
2104 url = urlparse.urlparse(self.path)
2105 port = url.port
2106 if not port:
2107 if url.scheme == 'http':
2108 port = 80
2109 elif url.scheme == 'https':
2110 port = 443
2111 if not url.hostname or not port:
2112 self.send_response(400)
2113 self.end_headers()
2114 return
2115
2116 if len(url.path) == 0:
2117 path = '/'
2118 else:
2119 path = url.path
2120 if len(url.query) > 0:
2121 path = '%s?%s' % (url.path, url.query)
2122
2123 sock = None
2124 try:
2125 sock = socket.create_connection((url.hostname, port))
2126 sock.send('%s %s %s\r\n' % (
2127 self.command, path, self.protocol_version))
2128 for header in self.headers.headers:
2129 header = header.strip()
2130 if (header.lower().startswith('connection') or
2131 header.lower().startswith('proxy')):
2132 continue
2133 sock.send('%s\r\n' % header)
2134 sock.send('\r\n')
2135 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002136 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002137 self.send_response(500)
2138 self.end_headers()
2139 finally:
2140 if sock is not None:
2141 sock.close()
2142
2143 def do_CONNECT(self):
2144 try:
2145 pos = self.path.rfind(':')
2146 host = self.path[:pos]
2147 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002148 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002149 self.send_response(400)
2150 self.end_headers()
2151
2152 try:
2153 sock = socket.create_connection((host, port))
2154 self.send_response(200, 'Connection established')
2155 self.end_headers()
2156 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002157 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002158 self.send_response(500)
2159 self.end_headers()
2160 finally:
2161 sock.close()
2162
2163 def do_GET(self):
2164 self._do_common_method()
2165
2166 def do_HEAD(self):
2167 self._do_common_method()
2168
2169
mattm@chromium.org830a3712012-11-07 23:00:07 +00002170class ServerRunner(testserver_base.TestServerRunner):
2171 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002172
mattm@chromium.org830a3712012-11-07 23:00:07 +00002173 def __init__(self):
2174 super(ServerRunner, self).__init__()
2175 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002176
mattm@chromium.org830a3712012-11-07 23:00:07 +00002177 def __make_data_dir(self):
2178 if self.options.data_dir:
2179 if not os.path.isdir(self.options.data_dir):
2180 raise testserver_base.OptionError('specified data dir not found: ' +
2181 self.options.data_dir + ' exiting...')
2182 my_data_dir = self.options.data_dir
2183 else:
2184 # Create the default path to our data dir, relative to the exe dir.
2185 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
2186 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002187
mattm@chromium.org830a3712012-11-07 23:00:07 +00002188 #TODO(ibrar): Must use Find* funtion defined in google\tools
2189 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002190
mattm@chromium.org830a3712012-11-07 23:00:07 +00002191 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00002192
mattm@chromium.org830a3712012-11-07 23:00:07 +00002193 def create_server(self, server_data):
2194 port = self.options.port
2195 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00002196
mattm@chromium.org830a3712012-11-07 23:00:07 +00002197 if self.options.server_type == SERVER_HTTP:
2198 if self.options.https:
2199 pem_cert_and_key = None
2200 if self.options.cert_and_key_file:
2201 if not os.path.isfile(self.options.cert_and_key_file):
2202 raise testserver_base.OptionError(
2203 'specified server cert file not found: ' +
2204 self.options.cert_and_key_file + ' exiting...')
2205 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002206 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002207 # generate a new certificate and run an OCSP server for it.
2208 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2209 print ('OCSP server started on %s:%d...' %
2210 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002211
mattm@chromium.org830a3712012-11-07 23:00:07 +00002212 ocsp_der = None
2213 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002214
mattm@chromium.org830a3712012-11-07 23:00:07 +00002215 if self.options.ocsp == 'ok':
2216 ocsp_state = minica.OCSP_STATE_GOOD
2217 elif self.options.ocsp == 'revoked':
2218 ocsp_state = minica.OCSP_STATE_REVOKED
2219 elif self.options.ocsp == 'invalid':
2220 ocsp_state = minica.OCSP_STATE_INVALID
2221 elif self.options.ocsp == 'unauthorized':
2222 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2223 elif self.options.ocsp == 'unknown':
2224 ocsp_state = minica.OCSP_STATE_UNKNOWN
2225 else:
2226 raise testserver_base.OptionError('unknown OCSP status: ' +
2227 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002228
mattm@chromium.org830a3712012-11-07 23:00:07 +00002229 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2230 subject = "127.0.0.1",
2231 ocsp_url = ("http://%s:%d/ocsp" %
2232 (host, self.__ocsp_server.server_port)),
2233 ocsp_state = ocsp_state)
2234
2235 self.__ocsp_server.ocsp_response = ocsp_der
2236
2237 for ca_cert in self.options.ssl_client_ca:
2238 if not os.path.isfile(ca_cert):
2239 raise testserver_base.OptionError(
2240 'specified trusted client CA file not found: ' + ca_cert +
2241 ' exiting...')
2242 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2243 self.options.ssl_client_auth,
2244 self.options.ssl_client_ca,
2245 self.options.ssl_bulk_cipher,
2246 self.options.record_resume,
2247 self.options.tls_intolerant)
2248 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
2249 else:
2250 server = HTTPServer((host, port), TestPageHandler)
2251 print 'HTTP server started on %s:%d...' % (host, server.server_port)
2252
2253 server.data_dir = self.__make_data_dir()
2254 server.file_root_url = self.options.file_root_url
2255 server_data['port'] = server.server_port
2256 server._device_management_handler = None
2257 server.policy_keys = self.options.policy_keys
mattm@chromium.org830a3712012-11-07 23:00:07 +00002258 server.gdata_auth_token = self.options.auth_token
2259 elif self.options.server_type == SERVER_WEBSOCKET:
2260 # Launch pywebsocket via WebSocketServer.
2261 logger = logging.getLogger()
2262 logger.addHandler(logging.StreamHandler())
2263 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2264 # is required to work correctly. It should be fixed from pywebsocket side.
2265 os.chdir(self.__make_data_dir())
2266 websocket_options = WebSocketOptions(host, port, '.')
2267 if self.options.cert_and_key_file:
2268 websocket_options.use_tls = True
2269 websocket_options.private_key = self.options.cert_and_key_file
2270 websocket_options.certificate = self.options.cert_and_key_file
2271 if self.options.ssl_client_auth:
2272 websocket_options.tls_client_auth = True
2273 if len(self.options.ssl_client_ca) != 1:
2274 raise testserver_base.OptionError(
2275 'one trusted client CA file should be specified')
2276 if not os.path.isfile(self.options.ssl_client_ca[0]):
2277 raise testserver_base.OptionError(
2278 'specified trusted client CA file not found: ' +
2279 self.options.ssl_client_ca[0] + ' exiting...')
2280 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2281 server = WebSocketServer(websocket_options)
2282 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2283 server_data['port'] = server.server_port
2284 elif self.options.server_type == SERVER_SYNC:
2285 xmpp_port = self.options.xmpp_port
2286 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2287 print 'Sync HTTP server started on port %d...' % server.server_port
2288 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2289 server_data['port'] = server.server_port
2290 server_data['xmpp_port'] = server.xmpp_port
2291 elif self.options.server_type == SERVER_TCP_ECHO:
2292 # Used for generating the key (randomly) that encodes the "echo request"
2293 # message.
2294 random.seed()
2295 server = TCPEchoServer((host, port), TCPEchoHandler)
2296 print 'Echo TCP server started on port %d...' % server.server_port
2297 server_data['port'] = server.server_port
2298 elif self.options.server_type == SERVER_UDP_ECHO:
2299 # Used for generating the key (randomly) that encodes the "echo request"
2300 # message.
2301 random.seed()
2302 server = UDPEchoServer((host, port), UDPEchoHandler)
2303 print 'Echo UDP server started on port %d...' % server.server_port
2304 server_data['port'] = server.server_port
2305 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2306 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2307 print 'BasicAuthProxy server started on port %d...' % server.server_port
2308 server_data['port'] = server.server_port
2309 elif self.options.server_type == SERVER_FTP:
2310 my_data_dir = self.__make_data_dir()
2311
2312 # Instantiate a dummy authorizer for managing 'virtual' users
2313 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2314
2315 # Define a new user having full r/w permissions and a read-only
2316 # anonymous user
2317 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2318
2319 authorizer.add_anonymous(my_data_dir)
2320
2321 # Instantiate FTP handler class
2322 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2323 ftp_handler.authorizer = authorizer
2324
2325 # Define a customized banner (string returned when client connects)
2326 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2327 pyftpdlib.ftpserver.__ver__)
2328
2329 # Instantiate FTP server class and listen to address:port
2330 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2331 server_data['port'] = server.socket.getsockname()[1]
2332 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002333 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002334 raise testserver_base.OptionError('unknown server type' +
2335 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002336
mattm@chromium.org830a3712012-11-07 23:00:07 +00002337 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002338
mattm@chromium.org830a3712012-11-07 23:00:07 +00002339 def run_server(self):
2340 if self.__ocsp_server:
2341 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002342
mattm@chromium.org830a3712012-11-07 23:00:07 +00002343 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002344
mattm@chromium.org830a3712012-11-07 23:00:07 +00002345 if self.__ocsp_server:
2346 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002347
mattm@chromium.org830a3712012-11-07 23:00:07 +00002348 def add_options(self):
2349 testserver_base.TestServerRunner.add_options(self)
2350 self.option_parser.add_option('-f', '--ftp', action='store_const',
2351 const=SERVER_FTP, default=SERVER_HTTP,
2352 dest='server_type',
2353 help='start up an FTP server.')
2354 self.option_parser.add_option('--sync', action='store_const',
2355 const=SERVER_SYNC, default=SERVER_HTTP,
2356 dest='server_type',
2357 help='start up a sync server.')
2358 self.option_parser.add_option('--tcp-echo', action='store_const',
2359 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2360 dest='server_type',
2361 help='start up a tcp echo server.')
2362 self.option_parser.add_option('--udp-echo', action='store_const',
2363 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2364 dest='server_type',
2365 help='start up a udp echo server.')
2366 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2367 const=SERVER_BASIC_AUTH_PROXY,
2368 default=SERVER_HTTP, dest='server_type',
2369 help='start up a proxy server which requires '
2370 'basic authentication.')
2371 self.option_parser.add_option('--websocket', action='store_const',
2372 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2373 dest='server_type',
2374 help='start up a WebSocket server.')
2375 self.option_parser.add_option('--xmpp-port', default='0', type='int',
2376 help='Port used by the XMPP server. If '
2377 'unspecified, the XMPP server will listen on '
2378 'an ephemeral port.')
2379 self.option_parser.add_option('--data-dir', dest='data_dir',
2380 help='Directory from which to read the '
2381 'files.')
2382 self.option_parser.add_option('--https', action='store_true',
2383 dest='https', help='Specify that https '
2384 'should be used.')
2385 self.option_parser.add_option('--cert-and-key-file',
2386 dest='cert_and_key_file', help='specify the '
2387 'path to the file containing the certificate '
2388 'and private key for the server in PEM '
2389 'format')
2390 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2391 help='The type of OCSP response generated '
2392 'for the automatically generated '
2393 'certificate. One of [ok,revoked,invalid]')
2394 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2395 default='0', type='int',
2396 help='If nonzero, certain TLS connections '
2397 'will be aborted in order to test version '
2398 'fallback. 1 means all TLS versions will be '
2399 'aborted. 2 means TLS 1.1 or higher will be '
2400 'aborted. 3 means TLS 1.2 or higher will be '
2401 'aborted.')
2402 self.option_parser.add_option('--https-record-resume',
2403 dest='record_resume', const=True,
2404 default=False, action='store_const',
2405 help='Record resumption cache events rather '
2406 'than resuming as normal. Allows the use of '
2407 'the /ssl-session-cache request')
2408 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2409 help='Require SSL client auth on every '
2410 'connection.')
2411 self.option_parser.add_option('--ssl-client-ca', action='append',
2412 default=[], help='Specify that the client '
2413 'certificate request should include the CA '
2414 'named in the subject of the DER-encoded '
2415 'certificate contained in the specified '
2416 'file. This option may appear multiple '
2417 'times, indicating multiple CA names should '
2418 'be sent in the request.')
2419 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2420 help='Specify the bulk encryption '
2421 'algorithm(s) that will be accepted by the '
2422 'SSL server. Valid values are "aes256", '
2423 '"aes128", "3des", "rc4". If omitted, all '
2424 'algorithms will be used. This option may '
2425 'appear multiple times, indicating '
2426 'multiple algorithms should be enabled.');
2427 self.option_parser.add_option('--file-root-url', default='/files/',
2428 help='Specify a root URL for files served.')
2429 self.option_parser.add_option('--policy-key', action='append',
2430 dest='policy_keys',
2431 help='Specify a path to a PEM-encoded '
2432 'private key to use for policy signing. May '
2433 'be specified multiple times in order to '
2434 'load multipe keys into the server. If the '
2435 'server has multiple keys, it will rotate '
2436 'through them in at each request a '
2437 'round-robin fashion. The server will '
2438 'generate a random key if none is specified '
2439 'on the command line.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002440 self.option_parser.add_option('--auth-token', dest='auth_token',
2441 help='Specify the auth token which should be '
2442 'used in the authorization header for GData.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002443
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002444
initial.commit94958cf2008-07-26 22:42:52 +00002445if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002446 sys.exit(ServerRunner().main())