blob: 55e7cd0af6c53dd084b7f1b62708b953e9b0ef28 [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
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000026import optparse
initial.commit94958cf2008-07-26 22:42:52 +000027import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000028import random
initial.commit94958cf2008-07-26 22:42:52 +000029import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000030import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000031import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import SocketServer
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000033import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000034import sys
35import threading
initial.commit94958cf2008-07-26 22:42:52 +000036import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000037import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000038import urlparse
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000039import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000040import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000041
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000042# Ignore deprecation warnings, they make our output more cluttered.
43warnings.filterwarnings("ignore", category=DeprecationWarning)
44
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000045import echo_message
toyoshim@chromium.org716d0572012-10-16 13:52:05 +000046
47# TODO(toyoshim): Some try bots for pyauto don't check out pywebsocket repos
48# unexpectedly. pyauto doesn't use WebSocket module, so just ignore
49# ImportError as a temporal workaround.
50# http://crbug.com/155918
51try:
52 from mod_pywebsocket.standalone import WebSocketServer
53except ImportError:
54 pass
55
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000056import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000057import tlslite
58import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000059
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000060if sys.platform == 'win32':
61 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000062
maruel@chromium.org756cf982009-03-05 12:46:38 +000063SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000064SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000065SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000066SERVER_TCP_ECHO = 3
67SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000068SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000069SERVER_WEBSOCKET = 6
70
71# Default request queue size for WebSocketServer.
72_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000073
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000074# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000075debug_output = sys.stderr
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +000076def debug(string):
77 debug_output.write(string + "\n")
initial.commit94958cf2008-07-26 22:42:52 +000078 debug_output.flush()
79
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000080class WebSocketOptions:
81 """Holds options for WebSocketServer."""
82
83 def __init__(self, host, port, data_dir):
84 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
85 self.server_host = host
86 self.port = port
87 self.websock_handlers = data_dir
88 self.scan_dir = None
89 self.allow_handlers_outside_root_dir = False
90 self.websock_handlers_map_file = None
91 self.cgi_directories = []
92 self.is_executable_method = None
93 self.allow_draft75 = False
94 self.strict = True
95
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000096 self.use_tls = False
97 self.private_key = None
98 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000099 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000100 self.tls_client_ca = None
101 self.use_basic_auth = False
102
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000103class RecordingSSLSessionCache(object):
104 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
105 lookups and inserts in order to test session cache behaviours."""
106
107 def __init__(self):
108 self.log = []
109
110 def __getitem__(self, sessionID):
111 self.log.append(('lookup', sessionID))
112 raise KeyError()
113
114 def __setitem__(self, sessionID, session):
115 self.log.append(('insert', sessionID))
116
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000117
118class ClientRestrictingServerMixIn:
119 """Implements verify_request to limit connections to our configured IP
120 address."""
121
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000122 def verify_request(self, _request, client_address):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123 return client_address[0] == self.server_address[0]
124
125
initial.commit94958cf2008-07-26 22:42:52 +0000126class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000128 to be exited cleanly (by setting its "stop" member to True)."""
129
130 def serve_forever(self):
131 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000132 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000133 while not self.stop:
134 self.handle_request()
135 self.socket.close()
136
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000137
138class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000139 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000140 verification."""
141
142 pass
143
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000144class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
145 """This is a specialization of HTTPServer that serves an
146 OCSP response"""
147
148 def serve_forever_on_thread(self):
149 self.thread = threading.Thread(target = self.serve_forever,
150 name = "OCSPServerThread")
151 self.thread.start()
152
153 def stop_serving(self):
154 self.shutdown()
155 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000156
157class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
158 ClientRestrictingServerMixIn,
159 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000160 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000161 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000162
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000163 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000164 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000165 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000166 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
167 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000168 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000169 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000170 self.tls_intolerant = tls_intolerant
171
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000172 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000173 s = open(ca_file).read()
174 x509 = tlslite.api.X509()
175 x509.parse(s)
176 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000177 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
178 if ssl_bulk_ciphers is not None:
179 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000180
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000181 if record_resume_info:
182 # If record_resume_info is true then we'll replace the session cache with
183 # an object that records the lookups and inserts that it sees.
184 self.session_cache = RecordingSSLSessionCache()
185 else:
186 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000187 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
188
189 def handshake(self, tlsConnection):
190 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000191
initial.commit94958cf2008-07-26 22:42:52 +0000192 try:
193 tlsConnection.handshakeServer(certChain=self.cert_chain,
194 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000195 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000196 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000197 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000198 reqCAs=self.ssl_client_cas,
199 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000200 tlsConnection.ignoreAbruptClose = True
201 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000202 except tlslite.api.TLSAbruptCloseError:
203 # Ignore abrupt close.
204 return True
initial.commit94958cf2008-07-26 22:42:52 +0000205 except tlslite.api.TLSError, error:
206 print "Handshake failure:", str(error)
207 return False
208
akalin@chromium.org154bb132010-11-12 02:20:27 +0000209
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000210class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000211 """An HTTP server that handles sync commands."""
212
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000213 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000214 # We import here to avoid pulling in chromiumsync's dependencies
215 # unless strictly necessary.
216 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000217 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000218 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000219 self._sync_handler = chromiumsync.TestServer()
220 self._xmpp_socket_map = {}
221 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000222 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000223 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000224 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000225
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000226 def GetXmppServer(self):
227 return self._xmpp_server
228
akalin@chromium.org154bb132010-11-12 02:20:27 +0000229 def HandleCommand(self, query, raw_request):
230 return self._sync_handler.HandleCommand(query, raw_request)
231
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000232 def HandleRequestNoBlock(self):
233 """Handles a single request.
234
235 Copied from SocketServer._handle_request_noblock().
236 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000237
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000238 try:
239 request, client_address = self.get_request()
240 except socket.error:
241 return
242 if self.verify_request(request, client_address):
243 try:
244 self.process_request(request, client_address)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000245 except Exception:
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000246 self.handle_error(request, client_address)
247 self.close_request(request)
248
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000249 def SetAuthenticated(self, auth_valid):
250 self.authenticated = auth_valid
251
252 def GetAuthenticated(self):
253 return self.authenticated
254
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000255 def serve_forever(self):
256 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
257 """
258
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000259 def HandleXmppSocket(fd, socket_map, handler):
260 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000261
262 Adapted from asyncore.read() et al.
263 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000264
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000265 xmpp_connection = socket_map.get(fd)
266 # This could happen if a previous handler call caused fd to get
267 # removed from socket_map.
268 if xmpp_connection is None:
269 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000270 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000271 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000272 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
273 raise
274 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000275 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000276
277 while True:
278 read_fds = [ self.fileno() ]
279 write_fds = []
280 exceptional_fds = []
281
282 for fd, xmpp_connection in self._xmpp_socket_map.items():
283 is_r = xmpp_connection.readable()
284 is_w = xmpp_connection.writable()
285 if is_r:
286 read_fds.append(fd)
287 if is_w:
288 write_fds.append(fd)
289 if is_r or is_w:
290 exceptional_fds.append(fd)
291
292 try:
293 read_fds, write_fds, exceptional_fds = (
294 select.select(read_fds, write_fds, exceptional_fds))
295 except select.error, err:
296 if err.args[0] != errno.EINTR:
297 raise
298 else:
299 continue
300
301 for fd in read_fds:
302 if fd == self.fileno():
303 self.HandleRequestNoBlock()
304 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000305 HandleXmppSocket(fd, self._xmpp_socket_map,
306 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000307
308 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000309 HandleXmppSocket(fd, self._xmpp_socket_map,
310 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000311
312 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000313 HandleXmppSocket(fd, self._xmpp_socket_map,
314 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000315
akalin@chromium.org154bb132010-11-12 02:20:27 +0000316
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000317class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
318 """This is a specialization of FTPServer that adds client verification."""
319
320 pass
321
322
323class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000324 """A TCP echo server that echoes back what it has received."""
325
326 def server_bind(self):
327 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000328
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000329 SocketServer.TCPServer.server_bind(self)
330 host, port = self.socket.getsockname()[:2]
331 self.server_name = socket.getfqdn(host)
332 self.server_port = port
333
334 def serve_forever(self):
335 self.stop = False
336 self.nonce_time = None
337 while not self.stop:
338 self.handle_request()
339 self.socket.close()
340
341
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000342class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000343 """A UDP echo server that echoes back what it has received."""
344
345 def server_bind(self):
346 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000347
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000348 SocketServer.UDPServer.server_bind(self)
349 host, port = self.socket.getsockname()[:2]
350 self.server_name = socket.getfqdn(host)
351 self.server_port = port
352
353 def serve_forever(self):
354 self.stop = False
355 self.nonce_time = None
356 while not self.stop:
357 self.handle_request()
358 self.socket.close()
359
360
akalin@chromium.org154bb132010-11-12 02:20:27 +0000361class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
362
363 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000364 connect_handlers, get_handlers, head_handlers, post_handlers,
365 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000366 self._connect_handlers = connect_handlers
367 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000368 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000369 self._post_handlers = post_handlers
370 self._put_handlers = put_handlers
371 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
372 self, request, client_address, socket_server)
373
374 def log_request(self, *args, **kwargs):
375 # Disable request logging to declutter test log output.
376 pass
377
378 def _ShouldHandleRequest(self, handler_name):
379 """Determines if the path can be handled by the handler.
380
381 We consider a handler valid if the path begins with the
382 handler name. It can optionally be followed by "?*", "/*".
383 """
384
385 pattern = re.compile('%s($|\?|/).*' % handler_name)
386 return pattern.match(self.path)
387
388 def do_CONNECT(self):
389 for handler in self._connect_handlers:
390 if handler():
391 return
392
393 def do_GET(self):
394 for handler in self._get_handlers:
395 if handler():
396 return
397
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000398 def do_HEAD(self):
399 for handler in self._head_handlers:
400 if handler():
401 return
402
akalin@chromium.org154bb132010-11-12 02:20:27 +0000403 def do_POST(self):
404 for handler in self._post_handlers:
405 if handler():
406 return
407
408 def do_PUT(self):
409 for handler in self._put_handlers:
410 if handler():
411 return
412
413
414class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000415
416 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000417 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000418 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000419 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000420 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000421 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000422 self.NoCacheMaxAgeTimeHandler,
423 self.NoCacheTimeHandler,
424 self.CacheTimeHandler,
425 self.CacheExpiresHandler,
426 self.CacheProxyRevalidateHandler,
427 self.CachePrivateHandler,
428 self.CachePublicHandler,
429 self.CacheSMaxAgeHandler,
430 self.CacheMustRevalidateHandler,
431 self.CacheMustRevalidateMaxAgeHandler,
432 self.CacheNoStoreHandler,
433 self.CacheNoStoreMaxAgeHandler,
434 self.CacheNoTransformHandler,
435 self.DownloadHandler,
436 self.DownloadFinishHandler,
437 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000438 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000439 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000440 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000441 self.GDataAuthHandler,
442 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000444 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000445 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000446 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000447 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.AuthBasicHandler,
449 self.AuthDigestHandler,
450 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000451 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000452 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000453 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000454 self.ServerRedirectHandler,
455 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000456 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000457 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000458 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000459 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000460 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000461 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000462 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000463 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000464 self.DeviceManagementHandler,
465 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000466 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000467 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000468 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000469 head_handlers = [
470 self.FileHandler,
471 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000472
maruel@google.come250a9b2009-03-10 17:39:46 +0000473 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000474 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000475 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000476 'gif': 'image/gif',
477 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000478 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000479 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000480 'pdf' : 'application/pdf',
481 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000482 }
initial.commit94958cf2008-07-26 22:42:52 +0000483 self._default_mime_type = 'text/html'
484
akalin@chromium.org154bb132010-11-12 02:20:27 +0000485 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000486 connect_handlers, get_handlers, head_handlers,
487 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000488
initial.commit94958cf2008-07-26 22:42:52 +0000489 def GetMIMETypeFromName(self, file_name):
490 """Returns the mime type for the specified file_name. So far it only looks
491 at the file extension."""
492
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000493 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000494 if len(extension) == 0:
495 # no extension.
496 return self._default_mime_type
497
ericroman@google.comc17ca532009-05-07 03:51:05 +0000498 # extension starts with a dot, so we need to remove it
499 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000500
initial.commit94958cf2008-07-26 22:42:52 +0000501 def NoCacheMaxAgeTimeHandler(self):
502 """This request handler yields a page with the title set to the current
503 system time, and no caching requested."""
504
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000505 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000506 return False
507
508 self.send_response(200)
509 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000510 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000511 self.end_headers()
512
maruel@google.come250a9b2009-03-10 17:39:46 +0000513 self.wfile.write('<html><head><title>%s</title></head></html>' %
514 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000515
516 return True
517
518 def NoCacheTimeHandler(self):
519 """This request handler yields a page with the title set to the current
520 system time, and no caching requested."""
521
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000522 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000523 return False
524
525 self.send_response(200)
526 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000527 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000528 self.end_headers()
529
maruel@google.come250a9b2009-03-10 17:39:46 +0000530 self.wfile.write('<html><head><title>%s</title></head></html>' %
531 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000532
533 return True
534
535 def CacheTimeHandler(self):
536 """This request handler yields a page with the title set to the current
537 system time, and allows caching for one minute."""
538
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000539 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000540 return False
541
542 self.send_response(200)
543 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000544 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000545 self.end_headers()
546
maruel@google.come250a9b2009-03-10 17:39:46 +0000547 self.wfile.write('<html><head><title>%s</title></head></html>' %
548 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000549
550 return True
551
552 def CacheExpiresHandler(self):
553 """This request handler yields a page with the title set to the current
554 system time, and set the page to expire on 1 Jan 2099."""
555
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000556 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 self.send_response(200)
560 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000561 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000562 self.end_headers()
563
maruel@google.come250a9b2009-03-10 17:39:46 +0000564 self.wfile.write('<html><head><title>%s</title></head></html>' %
565 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000566
567 return True
568
569 def CacheProxyRevalidateHandler(self):
570 """This request handler yields a page with the title set to the current
571 system time, and allows caching for 60 seconds"""
572
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000573 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000574 return False
575
576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000577 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000578 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
579 self.end_headers()
580
maruel@google.come250a9b2009-03-10 17:39:46 +0000581 self.wfile.write('<html><head><title>%s</title></head></html>' %
582 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000583
584 return True
585
586 def CachePrivateHandler(self):
587 """This request handler yields a page with the title set to the current
588 system time, and allows caching for 5 seconds."""
589
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000590 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000591 return False
592
593 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000594 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000595 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000596 self.end_headers()
597
maruel@google.come250a9b2009-03-10 17:39:46 +0000598 self.wfile.write('<html><head><title>%s</title></head></html>' %
599 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000600
601 return True
602
603 def CachePublicHandler(self):
604 """This request handler yields a page with the title set to the current
605 system time, and allows caching for 5 seconds."""
606
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000607 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000608 return False
609
610 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000611 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000612 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000613 self.end_headers()
614
maruel@google.come250a9b2009-03-10 17:39:46 +0000615 self.wfile.write('<html><head><title>%s</title></head></html>' %
616 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000617
618 return True
619
620 def CacheSMaxAgeHandler(self):
621 """This request handler yields a page with the title set to the current
622 system time, and does not allow for caching."""
623
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000624 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000625 return False
626
627 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000628 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000629 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
630 self.end_headers()
631
maruel@google.come250a9b2009-03-10 17:39:46 +0000632 self.wfile.write('<html><head><title>%s</title></head></html>' %
633 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000634
635 return True
636
637 def CacheMustRevalidateHandler(self):
638 """This request handler yields a page with the title set to the current
639 system time, and does not allow caching."""
640
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000641 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000642 return False
643
644 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000645 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000646 self.send_header('Cache-Control', 'must-revalidate')
647 self.end_headers()
648
maruel@google.come250a9b2009-03-10 17:39:46 +0000649 self.wfile.write('<html><head><title>%s</title></head></html>' %
650 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000651
652 return True
653
654 def CacheMustRevalidateMaxAgeHandler(self):
655 """This request handler yields a page with the title set to the current
656 system time, and does not allow caching event though max-age of 60
657 seconds is specified."""
658
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000659 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000660 return False
661
662 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000663 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000664 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
665 self.end_headers()
666
maruel@google.come250a9b2009-03-10 17:39:46 +0000667 self.wfile.write('<html><head><title>%s</title></head></html>' %
668 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000669
670 return True
671
initial.commit94958cf2008-07-26 22:42:52 +0000672 def CacheNoStoreHandler(self):
673 """This request handler yields a page with the title set to the current
674 system time, and does not allow the page to be stored."""
675
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000676 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000677 return False
678
679 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000680 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000681 self.send_header('Cache-Control', 'no-store')
682 self.end_headers()
683
maruel@google.come250a9b2009-03-10 17:39:46 +0000684 self.wfile.write('<html><head><title>%s</title></head></html>' %
685 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000686
687 return True
688
689 def CacheNoStoreMaxAgeHandler(self):
690 """This request handler yields a page with the title set to the current
691 system time, and does not allow the page to be stored even though max-age
692 of 60 seconds is specified."""
693
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000694 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000695 return False
696
697 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000698 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000699 self.send_header('Cache-Control', 'max-age=60, no-store')
700 self.end_headers()
701
maruel@google.come250a9b2009-03-10 17:39:46 +0000702 self.wfile.write('<html><head><title>%s</title></head></html>' %
703 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000704
705 return True
706
707
708 def CacheNoTransformHandler(self):
709 """This request handler yields a page with the title set to the current
710 system time, and does not allow the content to transformed during
711 user-agent caching"""
712
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000713 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000714 return False
715
716 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000717 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000718 self.send_header('Cache-Control', 'no-transform')
719 self.end_headers()
720
maruel@google.come250a9b2009-03-10 17:39:46 +0000721 self.wfile.write('<html><head><title>%s</title></head></html>' %
722 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000723
724 return True
725
726 def EchoHeader(self):
727 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000728
ananta@chromium.org219b2062009-10-23 16:09:41 +0000729 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000730
ananta@chromium.org56812d02011-04-07 17:52:05 +0000731 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000732 """This function echoes back the value of a specific request header while
733 allowing caching for 16 hours."""
734
ananta@chromium.org56812d02011-04-07 17:52:05 +0000735 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000736
737 def EchoHeaderHelper(self, echo_header):
738 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000739
ananta@chromium.org219b2062009-10-23 16:09:41 +0000740 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000741 return False
742
743 query_char = self.path.find('?')
744 if query_char != -1:
745 header_name = self.path[query_char+1:]
746
747 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000748 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000749 if echo_header == '/echoheadercache':
750 self.send_header('Cache-control', 'max-age=60000')
751 else:
752 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000753 # insert a vary header to properly indicate that the cachability of this
754 # request is subject to value of the request header being echoed.
755 if len(header_name) > 0:
756 self.send_header('Vary', header_name)
757 self.end_headers()
758
759 if len(header_name) > 0:
760 self.wfile.write(self.headers.getheader(header_name))
761
762 return True
763
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000764 def ReadRequestBody(self):
765 """This function reads the body of the current HTTP request, handling
766 both plain and chunked transfer encoded requests."""
767
768 if self.headers.getheader('transfer-encoding') != 'chunked':
769 length = int(self.headers.getheader('content-length'))
770 return self.rfile.read(length)
771
772 # Read the request body as chunks.
773 body = ""
774 while True:
775 line = self.rfile.readline()
776 length = int(line, 16)
777 if length == 0:
778 self.rfile.readline()
779 break
780 body += self.rfile.read(length)
781 self.rfile.read(2)
782 return body
783
initial.commit94958cf2008-07-26 22:42:52 +0000784 def EchoHandler(self):
785 """This handler just echoes back the payload of the request, for testing
786 form submission."""
787
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000788 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000789 return False
790
791 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000792 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000793 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000794 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000795 return True
796
797 def EchoTitleHandler(self):
798 """This handler is like Echo, but sets the page title to the request."""
799
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000800 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000801 return False
802
803 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000804 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000805 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000806 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000807 self.wfile.write('<html><head><title>')
808 self.wfile.write(request)
809 self.wfile.write('</title></head></html>')
810 return True
811
812 def EchoAllHandler(self):
813 """This handler yields a (more) human-readable page listing information
814 about the request header & contents."""
815
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000816 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000817 return False
818
819 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000820 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000821 self.end_headers()
822 self.wfile.write('<html><head><style>'
823 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
824 '</style></head><body>'
825 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000826 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000827 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000828
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000829 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000830 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000831 params = cgi.parse_qs(qs, keep_blank_values=1)
832
833 for param in params:
834 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000835
836 self.wfile.write('</pre>')
837
838 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
839
840 self.wfile.write('</body></html>')
841 return True
842
843 def DownloadHandler(self):
844 """This handler sends a downloadable file with or without reporting
845 the size (6K)."""
846
847 if self.path.startswith("/download-unknown-size"):
848 send_length = False
849 elif self.path.startswith("/download-known-size"):
850 send_length = True
851 else:
852 return False
853
854 #
855 # The test which uses this functionality is attempting to send
856 # small chunks of data to the client. Use a fairly large buffer
857 # so that we'll fill chrome's IO buffer enough to force it to
858 # actually write the data.
859 # See also the comments in the client-side of this test in
860 # download_uitest.cc
861 #
862 size_chunk1 = 35*1024
863 size_chunk2 = 10*1024
864
865 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000866 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000867 self.send_header('Cache-Control', 'max-age=0')
868 if send_length:
869 self.send_header('Content-Length', size_chunk1 + size_chunk2)
870 self.end_headers()
871
872 # First chunk of data:
873 self.wfile.write("*" * size_chunk1)
874 self.wfile.flush()
875
876 # handle requests until one of them clears this flag.
877 self.server.waitForDownload = True
878 while self.server.waitForDownload:
879 self.server.handle_request()
880
881 # Second chunk of data:
882 self.wfile.write("*" * size_chunk2)
883 return True
884
885 def DownloadFinishHandler(self):
886 """This handler just tells the server to finish the current download."""
887
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000888 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000889 return False
890
891 self.server.waitForDownload = False
892 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000893 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000894 self.send_header('Cache-Control', 'max-age=0')
895 self.end_headers()
896 return True
897
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000898 def _ReplaceFileData(self, data, query_parameters):
899 """Replaces matching substrings in a file.
900
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000901 If the 'replace_text' URL query parameter is present, it is expected to be
902 of the form old_text:new_text, which indicates that any old_text strings in
903 the file are replaced with new_text. Multiple 'replace_text' parameters may
904 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000905
906 If the parameters are not present, |data| is returned.
907 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000908
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000909 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000910 replace_text_values = query_dict.get('replace_text', [])
911 for replace_text_value in replace_text_values:
912 replace_text_args = replace_text_value.split(':')
913 if len(replace_text_args) != 2:
914 raise ValueError(
915 'replace_text must be of form old_text:new_text. Actual value: %s' %
916 replace_text_value)
917 old_text_b64, new_text_b64 = replace_text_args
918 old_text = base64.urlsafe_b64decode(old_text_b64)
919 new_text = base64.urlsafe_b64decode(new_text_b64)
920 data = data.replace(old_text, new_text)
921 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000922
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000923 def ZipFileHandler(self):
924 """This handler sends the contents of the requested file in compressed form.
925 Can pass in a parameter that specifies that the content length be
926 C - the compressed size (OK),
927 U - the uncompressed size (Non-standard, but handled),
928 S - less than compressed (OK because we keep going),
929 M - larger than compressed but less than uncompressed (an error),
930 L - larger than uncompressed (an error)
931 Example: compressedfiles/Picture_1.doc?C
932 """
933
934 prefix = "/compressedfiles/"
935 if not self.path.startswith(prefix):
936 return False
937
938 # Consume a request body if present.
939 if self.command == 'POST' or self.command == 'PUT' :
940 self.ReadRequestBody()
941
942 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
943
944 if not query in ('C', 'U', 'S', 'M', 'L'):
945 return False
946
947 sub_path = url_path[len(prefix):]
948 entries = sub_path.split('/')
949 file_path = os.path.join(self.server.data_dir, *entries)
950 if os.path.isdir(file_path):
951 file_path = os.path.join(file_path, 'index.html')
952
953 if not os.path.isfile(file_path):
954 print "File not found " + sub_path + " full path:" + file_path
955 self.send_error(404)
956 return True
957
958 f = open(file_path, "rb")
959 data = f.read()
960 uncompressed_len = len(data)
961 f.close()
962
963 # Compress the data.
964 data = zlib.compress(data)
965 compressed_len = len(data)
966
967 content_length = compressed_len
968 if query == 'U':
969 content_length = uncompressed_len
970 elif query == 'S':
971 content_length = compressed_len / 2
972 elif query == 'M':
973 content_length = (compressed_len + uncompressed_len) / 2
974 elif query == 'L':
975 content_length = compressed_len + uncompressed_len
976
977 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000978 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000979 self.send_header('Content-encoding', 'deflate')
980 self.send_header('Connection', 'close')
981 self.send_header('Content-Length', content_length)
982 self.send_header('ETag', '\'' + file_path + '\'')
983 self.end_headers()
984
985 self.wfile.write(data)
986
987 return True
988
initial.commit94958cf2008-07-26 22:42:52 +0000989 def FileHandler(self):
990 """This handler sends the contents of the requested file. Wow, it's like
991 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000992
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000993 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000994 if not self.path.startswith(prefix):
995 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000996 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000997
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000998 def PostOnlyFileHandler(self):
999 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001000
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +00001001 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001002 if not self.path.startswith(prefix):
1003 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001004 return self._FileHandlerHelper(prefix)
1005
1006 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001007 request_body = ''
1008 if self.command == 'POST' or self.command == 'PUT':
1009 # Consume a request body if present.
1010 request_body = self.ReadRequestBody()
1011
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001012 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001013 query_dict = cgi.parse_qs(query)
1014
1015 expected_body = query_dict.get('expected_body', [])
1016 if expected_body and request_body not in expected_body:
1017 self.send_response(404)
1018 self.end_headers()
1019 self.wfile.write('')
1020 return True
1021
1022 expected_headers = query_dict.get('expected_headers', [])
1023 for expected_header in expected_headers:
1024 header_name, expected_value = expected_header.split(':')
1025 if self.headers.getheader(header_name) != expected_value:
1026 self.send_response(404)
1027 self.end_headers()
1028 self.wfile.write('')
1029 return True
1030
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001031 sub_path = url_path[len(prefix):]
1032 entries = sub_path.split('/')
1033 file_path = os.path.join(self.server.data_dir, *entries)
1034 if os.path.isdir(file_path):
1035 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001036
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001037 if not os.path.isfile(file_path):
1038 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001039 self.send_error(404)
1040 return True
1041
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001042 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001043 data = f.read()
1044 f.close()
1045
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001046 data = self._ReplaceFileData(data, query)
1047
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001048 old_protocol_version = self.protocol_version
1049
initial.commit94958cf2008-07-26 22:42:52 +00001050 # If file.mock-http-headers exists, it contains the headers we
1051 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001052 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001053 if os.path.isfile(headers_path):
1054 f = open(headers_path, "r")
1055
1056 # "HTTP/1.1 200 OK"
1057 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001058 http_major, http_minor, status_code = re.findall(
1059 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1060 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001061 self.send_response(int(status_code))
1062
1063 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001064 header_values = re.findall('(\S+):\s*(.*)', line)
1065 if len(header_values) > 0:
1066 # "name: value"
1067 name, value = header_values[0]
1068 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001069 f.close()
1070 else:
1071 # Could be more generic once we support mime-type sniffing, but for
1072 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001073
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001074 range_header = self.headers.get('Range')
1075 if range_header and range_header.startswith('bytes='):
1076 # Note this doesn't handle all valid byte range_header values (i.e.
1077 # left open ended ones), just enough for what we needed so far.
1078 range_header = range_header[6:].split('-')
1079 start = int(range_header[0])
1080 if range_header[1]:
1081 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001082 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001083 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001084
1085 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001086 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1087 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001088 self.send_header('Content-Range', content_range)
1089 data = data[start: end + 1]
1090 else:
1091 self.send_response(200)
1092
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001093 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001094 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001095 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001096 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001097 self.end_headers()
1098
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001099 if (self.command != 'HEAD'):
1100 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001101
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001102 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001103 return True
1104
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001105 def SetCookieHandler(self):
1106 """This handler just sets a cookie, for testing cookie handling."""
1107
1108 if not self._ShouldHandleRequest("/set-cookie"):
1109 return False
1110
1111 query_char = self.path.find('?')
1112 if query_char != -1:
1113 cookie_values = self.path[query_char + 1:].split('&')
1114 else:
1115 cookie_values = ("",)
1116 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001117 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001118 for cookie_value in cookie_values:
1119 self.send_header('Set-Cookie', '%s' % cookie_value)
1120 self.end_headers()
1121 for cookie_value in cookie_values:
1122 self.wfile.write('%s' % cookie_value)
1123 return True
1124
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001125 def SetManyCookiesHandler(self):
1126 """This handler just sets a given number of cookies, for testing handling
1127 of large numbers of cookies."""
1128
1129 if not self._ShouldHandleRequest("/set-many-cookies"):
1130 return False
1131
1132 query_char = self.path.find('?')
1133 if query_char != -1:
1134 num_cookies = int(self.path[query_char + 1:])
1135 else:
1136 num_cookies = 0
1137 self.send_response(200)
1138 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001139 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001140 self.send_header('Set-Cookie', 'a=')
1141 self.end_headers()
1142 self.wfile.write('%d cookies were sent' % num_cookies)
1143 return True
1144
mattm@chromium.org983fc462012-06-30 00:52:08 +00001145 def ExpectAndSetCookieHandler(self):
1146 """Expects some cookies to be sent, and if they are, sets more cookies.
1147
1148 The expect parameter specifies a required cookie. May be specified multiple
1149 times.
1150 The set parameter specifies a cookie to set if all required cookies are
1151 preset. May be specified multiple times.
1152 The data parameter specifies the response body data to be returned."""
1153
1154 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1155 return False
1156
1157 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1158 query_dict = cgi.parse_qs(query)
1159 cookies = set()
1160 if 'Cookie' in self.headers:
1161 cookie_header = self.headers.getheader('Cookie')
1162 cookies.update([s.strip() for s in cookie_header.split(';')])
1163 got_all_expected_cookies = True
1164 for expected_cookie in query_dict.get('expect', []):
1165 if expected_cookie not in cookies:
1166 got_all_expected_cookies = False
1167 self.send_response(200)
1168 self.send_header('Content-Type', 'text/html')
1169 if got_all_expected_cookies:
1170 for cookie_value in query_dict.get('set', []):
1171 self.send_header('Set-Cookie', '%s' % cookie_value)
1172 self.end_headers()
1173 for data_value in query_dict.get('data', []):
1174 self.wfile.write(data_value)
1175 return True
1176
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001177 def SetHeaderHandler(self):
1178 """This handler sets a response header. Parameters are in the
1179 key%3A%20value&key2%3A%20value2 format."""
1180
1181 if not self._ShouldHandleRequest("/set-header"):
1182 return False
1183
1184 query_char = self.path.find('?')
1185 if query_char != -1:
1186 headers_values = self.path[query_char + 1:].split('&')
1187 else:
1188 headers_values = ("",)
1189 self.send_response(200)
1190 self.send_header('Content-Type', 'text/html')
1191 for header_value in headers_values:
1192 header_value = urllib.unquote(header_value)
1193 (key, value) = header_value.split(': ', 1)
1194 self.send_header(key, value)
1195 self.end_headers()
1196 for header_value in headers_values:
1197 self.wfile.write('%s' % header_value)
1198 return True
1199
initial.commit94958cf2008-07-26 22:42:52 +00001200 def AuthBasicHandler(self):
1201 """This handler tests 'Basic' authentication. It just sends a page with
1202 title 'user/pass' if you succeed."""
1203
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001204 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001205 return False
1206
1207 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001208 expected_password = 'secret'
1209 realm = 'testrealm'
1210 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001211
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001212 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1213 query_params = cgi.parse_qs(query, True)
1214 if 'set-cookie-if-challenged' in query_params:
1215 set_cookie_if_challenged = True
1216 if 'password' in query_params:
1217 expected_password = query_params['password'][0]
1218 if 'realm' in query_params:
1219 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001220
initial.commit94958cf2008-07-26 22:42:52 +00001221 auth = self.headers.getheader('authorization')
1222 try:
1223 if not auth:
1224 raise Exception('no auth')
1225 b64str = re.findall(r'Basic (\S+)', auth)[0]
1226 userpass = base64.b64decode(b64str)
1227 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001228 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001229 raise Exception('wrong password')
1230 except Exception, e:
1231 # Authentication failed.
1232 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001233 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001234 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001235 if set_cookie_if_challenged:
1236 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001237 self.end_headers()
1238 self.wfile.write('<html><head>')
1239 self.wfile.write('<title>Denied: %s</title>' % e)
1240 self.wfile.write('</head><body>')
1241 self.wfile.write('auth=%s<p>' % auth)
1242 self.wfile.write('b64str=%s<p>' % b64str)
1243 self.wfile.write('username: %s<p>' % username)
1244 self.wfile.write('userpass: %s<p>' % userpass)
1245 self.wfile.write('password: %s<p>' % password)
1246 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1247 self.wfile.write('</body></html>')
1248 return True
1249
1250 # Authentication successful. (Return a cachable response to allow for
1251 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001252 old_protocol_version = self.protocol_version
1253 self.protocol_version = "HTTP/1.1"
1254
initial.commit94958cf2008-07-26 22:42:52 +00001255 if_none_match = self.headers.getheader('if-none-match')
1256 if if_none_match == "abc":
1257 self.send_response(304)
1258 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001259 elif url_path.endswith(".gif"):
1260 # Using chrome/test/data/google/logo.gif as the test image
1261 test_image_path = ['google', 'logo.gif']
1262 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1263 if not os.path.isfile(gif_path):
1264 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001265 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001266 return True
1267
1268 f = open(gif_path, "rb")
1269 data = f.read()
1270 f.close()
1271
1272 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001273 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001274 self.send_header('Cache-control', 'max-age=60000')
1275 self.send_header('Etag', 'abc')
1276 self.end_headers()
1277 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001278 else:
1279 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001280 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001281 self.send_header('Cache-control', 'max-age=60000')
1282 self.send_header('Etag', 'abc')
1283 self.end_headers()
1284 self.wfile.write('<html><head>')
1285 self.wfile.write('<title>%s/%s</title>' % (username, password))
1286 self.wfile.write('</head><body>')
1287 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001288 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001289 self.wfile.write('</body></html>')
1290
rvargas@google.com54453b72011-05-19 01:11:11 +00001291 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001292 return True
1293
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001294 def GDataAuthHandler(self):
1295 """This handler verifies the Authentication header for GData requests."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001296
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001297 if not self.server.gdata_auth_token:
1298 # --auth-token is not specified, not the test case for GData.
1299 return False
1300
1301 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1302 return False
1303
1304 if 'GData-Version' not in self.headers:
1305 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1306 return True
1307
1308 if 'Authorization' not in self.headers:
1309 self.send_error(httplib.UNAUTHORIZED)
1310 return True
1311
1312 field_prefix = 'Bearer '
1313 authorization = self.headers['Authorization']
1314 if not authorization.startswith(field_prefix):
1315 self.send_error(httplib.UNAUTHORIZED)
1316 return True
1317
1318 code = authorization[len(field_prefix):]
1319 if code != self.server.gdata_auth_token:
1320 self.send_error(httplib.UNAUTHORIZED)
1321 return True
1322
1323 return False
1324
1325 def GDataDocumentsFeedQueryHandler(self):
1326 """This handler verifies if required parameters are properly
1327 specified for the GData DocumentsFeed request."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001328
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001329 if not self.server.gdata_auth_token:
1330 # --auth-token is not specified, not the test case for GData.
1331 return False
1332
1333 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1334 return False
1335
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001336 (_path, _question, query_params) = self.path.partition('?')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001337 self.query_params = urlparse.parse_qs(query_params)
1338
1339 if 'v' not in self.query_params:
1340 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1341 return True
1342 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1343 # currently our GData client only uses JSON format.
1344 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1345 return True
1346
1347 return False
1348
tonyg@chromium.org75054202010-03-31 22:06:10 +00001349 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001350 """Returns a nonce that's stable per request path for the server's lifetime.
1351 This is a fake implementation. A real implementation would only use a given
1352 nonce a single time (hence the name n-once). However, for the purposes of
1353 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001354
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001355 Args:
1356 force_reset: Iff set, the nonce will be changed. Useful for testing the
1357 "stale" response.
1358 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001359
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001360 if force_reset or not self.server.nonce_time:
1361 self.server.nonce_time = time.time()
1362 return hashlib.md5('privatekey%s%d' %
1363 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001364
1365 def AuthDigestHandler(self):
1366 """This handler tests 'Digest' authentication.
1367
1368 It just sends a page with title 'user/pass' if you succeed.
1369
1370 A stale response is sent iff "stale" is present in the request path.
1371 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001372
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001373 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001374 return False
1375
tonyg@chromium.org75054202010-03-31 22:06:10 +00001376 stale = 'stale' in self.path
1377 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001378 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001379 password = 'secret'
1380 realm = 'testrealm'
1381
1382 auth = self.headers.getheader('authorization')
1383 pairs = {}
1384 try:
1385 if not auth:
1386 raise Exception('no auth')
1387 if not auth.startswith('Digest'):
1388 raise Exception('not digest')
1389 # Pull out all the name="value" pairs as a dictionary.
1390 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1391
1392 # Make sure it's all valid.
1393 if pairs['nonce'] != nonce:
1394 raise Exception('wrong nonce')
1395 if pairs['opaque'] != opaque:
1396 raise Exception('wrong opaque')
1397
1398 # Check the 'response' value and make sure it matches our magic hash.
1399 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001400 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001401 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001402 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001403 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001404 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001405 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1406 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001407 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001408
1409 if pairs['response'] != response:
1410 raise Exception('wrong password')
1411 except Exception, e:
1412 # Authentication failed.
1413 self.send_response(401)
1414 hdr = ('Digest '
1415 'realm="%s", '
1416 'domain="/", '
1417 'qop="auth", '
1418 'algorithm=MD5, '
1419 'nonce="%s", '
1420 'opaque="%s"') % (realm, nonce, opaque)
1421 if stale:
1422 hdr += ', stale="TRUE"'
1423 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001424 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001425 self.end_headers()
1426 self.wfile.write('<html><head>')
1427 self.wfile.write('<title>Denied: %s</title>' % e)
1428 self.wfile.write('</head><body>')
1429 self.wfile.write('auth=%s<p>' % auth)
1430 self.wfile.write('pairs=%s<p>' % pairs)
1431 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1432 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1433 self.wfile.write('</body></html>')
1434 return True
1435
1436 # Authentication successful.
1437 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001438 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001439 self.end_headers()
1440 self.wfile.write('<html><head>')
1441 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1442 self.wfile.write('</head><body>')
1443 self.wfile.write('auth=%s<p>' % auth)
1444 self.wfile.write('pairs=%s<p>' % pairs)
1445 self.wfile.write('</body></html>')
1446
1447 return True
1448
1449 def SlowServerHandler(self):
1450 """Wait for the user suggested time before responding. The syntax is
1451 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001452
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001453 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001454 return False
1455 query_char = self.path.find('?')
1456 wait_sec = 1.0
1457 if query_char >= 0:
1458 try:
1459 wait_sec = int(self.path[query_char + 1:])
1460 except ValueError:
1461 pass
1462 time.sleep(wait_sec)
1463 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001464 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001465 self.end_headers()
1466 self.wfile.write("waited %d seconds" % wait_sec)
1467 return True
1468
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001469 def ChunkedServerHandler(self):
1470 """Send chunked response. Allows to specify chunks parameters:
1471 - waitBeforeHeaders - ms to wait before sending headers
1472 - waitBetweenChunks - ms to wait between chunks
1473 - chunkSize - size of each chunk in bytes
1474 - chunksNumber - number of chunks
1475 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1476 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001477
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001478 if not self._ShouldHandleRequest("/chunked"):
1479 return False
1480 query_char = self.path.find('?')
1481 chunkedSettings = {'waitBeforeHeaders' : 0,
1482 'waitBetweenChunks' : 0,
1483 'chunkSize' : 5,
1484 'chunksNumber' : 5}
1485 if query_char >= 0:
1486 params = self.path[query_char + 1:].split('&')
1487 for param in params:
1488 keyValue = param.split('=')
1489 if len(keyValue) == 2:
1490 try:
1491 chunkedSettings[keyValue[0]] = int(keyValue[1])
1492 except ValueError:
1493 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001494 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001495 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1496 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001497 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001498 self.send_header('Connection', 'close')
1499 self.send_header('Transfer-Encoding', 'chunked')
1500 self.end_headers()
1501 # Chunked encoding: sending all chunks, then final zero-length chunk and
1502 # then final CRLF.
1503 for i in range(0, chunkedSettings['chunksNumber']):
1504 if i > 0:
1505 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1506 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001507 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001508 self.sendChunkHelp('')
1509 return True
1510
initial.commit94958cf2008-07-26 22:42:52 +00001511 def ContentTypeHandler(self):
1512 """Returns a string of html with the given content type. E.g.,
1513 /contenttype?text/css returns an html file with the Content-Type
1514 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001515
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001516 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001517 return False
1518 query_char = self.path.find('?')
1519 content_type = self.path[query_char + 1:].strip()
1520 if not content_type:
1521 content_type = 'text/html'
1522 self.send_response(200)
1523 self.send_header('Content-Type', content_type)
1524 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001525 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001526 return True
1527
creis@google.com2f4f6a42011-03-25 19:44:19 +00001528 def NoContentHandler(self):
1529 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001530
creis@google.com2f4f6a42011-03-25 19:44:19 +00001531 if not self._ShouldHandleRequest("/nocontent"):
1532 return False
1533 self.send_response(204)
1534 self.end_headers()
1535 return True
1536
initial.commit94958cf2008-07-26 22:42:52 +00001537 def ServerRedirectHandler(self):
1538 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001539 '/server-redirect?http://foo.bar/asdf' to redirect to
1540 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001541
1542 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001543 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001544 return False
1545
1546 query_char = self.path.find('?')
1547 if query_char < 0 or len(self.path) <= query_char + 1:
1548 self.sendRedirectHelp(test_name)
1549 return True
1550 dest = self.path[query_char + 1:]
1551
1552 self.send_response(301) # moved permanently
1553 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001554 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001555 self.end_headers()
1556 self.wfile.write('<html><head>')
1557 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1558
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001559 return True
initial.commit94958cf2008-07-26 22:42:52 +00001560
1561 def ClientRedirectHandler(self):
1562 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001563 '/client-redirect?http://foo.bar/asdf' to redirect to
1564 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001565
1566 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001567 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001568 return False
1569
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001570 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001571 if query_char < 0 or len(self.path) <= query_char + 1:
1572 self.sendRedirectHelp(test_name)
1573 return True
1574 dest = self.path[query_char + 1:]
1575
1576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001577 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001578 self.end_headers()
1579 self.wfile.write('<html><head>')
1580 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1581 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1582
1583 return True
1584
tony@chromium.org03266982010-03-05 03:18:42 +00001585 def MultipartHandler(self):
1586 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001587
tony@chromium.org4cb88302011-09-27 22:13:49 +00001588 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001589 if not self._ShouldHandleRequest(test_name):
1590 return False
1591
1592 num_frames = 10
1593 bound = '12345'
1594 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001595 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001596 'multipart/x-mixed-replace;boundary=' + bound)
1597 self.end_headers()
1598
1599 for i in xrange(num_frames):
1600 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001601 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001602 self.wfile.write('<title>page ' + str(i) + '</title>')
1603 self.wfile.write('page ' + str(i))
1604
1605 self.wfile.write('--' + bound + '--')
1606 return True
1607
tony@chromium.org4cb88302011-09-27 22:13:49 +00001608 def MultipartSlowHandler(self):
1609 """Send a multipart response (3 text/html pages) with a slight delay
1610 between each page. This is similar to how some pages show status using
1611 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001612
tony@chromium.org4cb88302011-09-27 22:13:49 +00001613 test_name = '/multipart-slow'
1614 if not self._ShouldHandleRequest(test_name):
1615 return False
1616
1617 num_frames = 3
1618 bound = '12345'
1619 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001620 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001621 'multipart/x-mixed-replace;boundary=' + bound)
1622 self.end_headers()
1623
1624 for i in xrange(num_frames):
1625 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001626 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001627 time.sleep(0.25)
1628 if i == 2:
1629 self.wfile.write('<title>PASS</title>')
1630 else:
1631 self.wfile.write('<title>page ' + str(i) + '</title>')
1632 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1633
1634 self.wfile.write('--' + bound + '--')
1635 return True
1636
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001637 def GetSSLSessionCacheHandler(self):
1638 """Send a reply containing a log of the session cache operations."""
1639
1640 if not self._ShouldHandleRequest('/ssl-session-cache'):
1641 return False
1642
1643 self.send_response(200)
1644 self.send_header('Content-Type', 'text/plain')
1645 self.end_headers()
1646 try:
1647 for (action, sessionID) in self.server.session_cache.log:
1648 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001649 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001650 self.wfile.write('Pass --https-record-resume in order to use' +
1651 ' this request')
1652 return True
1653
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001654 def CloseSocketHandler(self):
1655 """Closes the socket without sending anything."""
1656
1657 if not self._ShouldHandleRequest('/close-socket'):
1658 return False
1659
1660 self.wfile.close()
1661 return True
1662
initial.commit94958cf2008-07-26 22:42:52 +00001663 def DefaultResponseHandler(self):
1664 """This is the catch-all response handler for requests that aren't handled
1665 by one of the special handlers above.
1666 Note that we specify the content-length as without it the https connection
1667 is not closed properly (and the browser keeps expecting data)."""
1668
1669 contents = "Default response given for path: " + self.path
1670 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001671 self.send_header('Content-Type', 'text/html')
1672 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001673 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001674 if (self.command != 'HEAD'):
1675 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001676 return True
1677
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001678 def RedirectConnectHandler(self):
1679 """Sends a redirect to the CONNECT request for www.redirect.com. This
1680 response is not specified by the RFC, so the browser should not follow
1681 the redirect."""
1682
1683 if (self.path.find("www.redirect.com") < 0):
1684 return False
1685
1686 dest = "http://www.destination.com/foo.js"
1687
1688 self.send_response(302) # moved temporarily
1689 self.send_header('Location', dest)
1690 self.send_header('Connection', 'close')
1691 self.end_headers()
1692 return True
1693
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001694 def ServerAuthConnectHandler(self):
1695 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1696 response doesn't make sense because the proxy server cannot request
1697 server authentication."""
1698
1699 if (self.path.find("www.server-auth.com") < 0):
1700 return False
1701
1702 challenge = 'Basic realm="WallyWorld"'
1703
1704 self.send_response(401) # unauthorized
1705 self.send_header('WWW-Authenticate', challenge)
1706 self.send_header('Connection', 'close')
1707 self.end_headers()
1708 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001709
1710 def DefaultConnectResponseHandler(self):
1711 """This is the catch-all response handler for CONNECT requests that aren't
1712 handled by one of the special handlers above. Real Web servers respond
1713 with 400 to CONNECT requests."""
1714
1715 contents = "Your client has issued a malformed or illegal request."
1716 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001717 self.send_header('Content-Type', 'text/html')
1718 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001719 self.end_headers()
1720 self.wfile.write(contents)
1721 return True
1722
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001723 def DeviceManagementHandler(self):
1724 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001725
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001726 if not self._ShouldHandleRequest("/device_management"):
1727 return False
1728
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001729 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001730
1731 if not self.server._device_management_handler:
1732 import device_management
1733 policy_path = os.path.join(self.server.data_dir, 'device_management')
1734 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001735 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001736 self.server.policy_keys,
1737 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001738
1739 http_response, raw_reply = (
1740 self.server._device_management_handler.HandleRequest(self.path,
1741 self.headers,
1742 raw_request))
1743 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001744 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001745 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001746 self.end_headers()
1747 self.wfile.write(raw_reply)
1748 return True
1749
initial.commit94958cf2008-07-26 22:42:52 +00001750 # called by the redirect handling function when there is no parameter
1751 def sendRedirectHelp(self, redirect_name):
1752 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001753 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001754 self.end_headers()
1755 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1756 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1757 self.wfile.write('</body></html>')
1758
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001759 # called by chunked handling function
1760 def sendChunkHelp(self, chunk):
1761 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1762 self.wfile.write('%X\r\n' % len(chunk))
1763 self.wfile.write(chunk)
1764 self.wfile.write('\r\n')
1765
akalin@chromium.org154bb132010-11-12 02:20:27 +00001766
1767class SyncPageHandler(BasePageHandler):
1768 """Handler for the main HTTP sync server."""
1769
1770 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001771 get_handlers = [self.ChromiumSyncTimeHandler,
1772 self.ChromiumSyncMigrationOpHandler,
1773 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001774 self.ChromiumSyncDisableNotificationsOpHandler,
1775 self.ChromiumSyncEnableNotificationsOpHandler,
1776 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001777 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001778 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001779 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001780 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001781 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001782
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001783 post_handlers = [self.ChromiumSyncCommandHandler,
1784 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001785 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001786 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001787 post_handlers, [])
1788
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001789
akalin@chromium.org154bb132010-11-12 02:20:27 +00001790 def ChromiumSyncTimeHandler(self):
1791 """Handle Chromium sync .../time requests.
1792
1793 The syncer sometimes checks server reachability by examining /time.
1794 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001795
akalin@chromium.org154bb132010-11-12 02:20:27 +00001796 test_name = "/chromiumsync/time"
1797 if not self._ShouldHandleRequest(test_name):
1798 return False
1799
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001800 # Chrome hates it if we send a response before reading the request.
1801 if self.headers.getheader('content-length'):
1802 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001803 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001804
akalin@chromium.org154bb132010-11-12 02:20:27 +00001805 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001806 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001807 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001808 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001809 return True
1810
1811 def ChromiumSyncCommandHandler(self):
1812 """Handle a chromiumsync command arriving via http.
1813
1814 This covers all sync protocol commands: authentication, getupdates, and
1815 commit.
1816 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001817
akalin@chromium.org154bb132010-11-12 02:20:27 +00001818 test_name = "/chromiumsync/command"
1819 if not self._ShouldHandleRequest(test_name):
1820 return False
1821
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001822 length = int(self.headers.getheader('content-length'))
1823 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001824 http_response = 200
1825 raw_reply = None
1826 if not self.server.GetAuthenticated():
1827 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001828 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1829 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001830 else:
1831 http_response, raw_reply = self.server.HandleCommand(
1832 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001833
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001834 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001835 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001836 if http_response == 401:
1837 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001838 self.end_headers()
1839 self.wfile.write(raw_reply)
1840 return True
1841
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001842 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001843 test_name = "/chromiumsync/migrate"
1844 if not self._ShouldHandleRequest(test_name):
1845 return False
1846
1847 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1848 self.path)
1849 self.send_response(http_response)
1850 self.send_header('Content-Type', 'text/html')
1851 self.send_header('Content-Length', len(raw_reply))
1852 self.end_headers()
1853 self.wfile.write(raw_reply)
1854 return True
1855
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001856 def ChromiumSyncCredHandler(self):
1857 test_name = "/chromiumsync/cred"
1858 if not self._ShouldHandleRequest(test_name):
1859 return False
1860 try:
1861 query = urlparse.urlparse(self.path)[4]
1862 cred_valid = urlparse.parse_qs(query)['valid']
1863 if cred_valid[0] == 'True':
1864 self.server.SetAuthenticated(True)
1865 else:
1866 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001867 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001868 self.server.SetAuthenticated(False)
1869
1870 http_response = 200
1871 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1872 self.send_response(http_response)
1873 self.send_header('Content-Type', 'text/html')
1874 self.send_header('Content-Length', len(raw_reply))
1875 self.end_headers()
1876 self.wfile.write(raw_reply)
1877 return True
1878
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001879 def ChromiumSyncDisableNotificationsOpHandler(self):
1880 test_name = "/chromiumsync/disablenotifications"
1881 if not self._ShouldHandleRequest(test_name):
1882 return False
1883 self.server.GetXmppServer().DisableNotifications()
1884 result = 200
1885 raw_reply = ('<html><title>Notifications disabled</title>'
1886 '<H1>Notifications disabled</H1></html>')
1887 self.send_response(result)
1888 self.send_header('Content-Type', 'text/html')
1889 self.send_header('Content-Length', len(raw_reply))
1890 self.end_headers()
1891 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001892 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001893
1894 def ChromiumSyncEnableNotificationsOpHandler(self):
1895 test_name = "/chromiumsync/enablenotifications"
1896 if not self._ShouldHandleRequest(test_name):
1897 return False
1898 self.server.GetXmppServer().EnableNotifications()
1899 result = 200
1900 raw_reply = ('<html><title>Notifications enabled</title>'
1901 '<H1>Notifications enabled</H1></html>')
1902 self.send_response(result)
1903 self.send_header('Content-Type', 'text/html')
1904 self.send_header('Content-Length', len(raw_reply))
1905 self.end_headers()
1906 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001907 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001908
1909 def ChromiumSyncSendNotificationOpHandler(self):
1910 test_name = "/chromiumsync/sendnotification"
1911 if not self._ShouldHandleRequest(test_name):
1912 return False
1913 query = urlparse.urlparse(self.path)[4]
1914 query_params = urlparse.parse_qs(query)
1915 channel = ''
1916 data = ''
1917 if 'channel' in query_params:
1918 channel = query_params['channel'][0]
1919 if 'data' in query_params:
1920 data = query_params['data'][0]
1921 self.server.GetXmppServer().SendNotification(channel, data)
1922 result = 200
1923 raw_reply = ('<html><title>Notification sent</title>'
1924 '<H1>Notification sent with channel "%s" '
1925 'and data "%s"</H1></html>'
1926 % (channel, data))
1927 self.send_response(result)
1928 self.send_header('Content-Type', 'text/html')
1929 self.send_header('Content-Length', len(raw_reply))
1930 self.end_headers()
1931 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001932 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001933
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001934 def ChromiumSyncBirthdayErrorOpHandler(self):
1935 test_name = "/chromiumsync/birthdayerror"
1936 if not self._ShouldHandleRequest(test_name):
1937 return False
1938 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1939 self.send_response(result)
1940 self.send_header('Content-Type', 'text/html')
1941 self.send_header('Content-Length', len(raw_reply))
1942 self.end_headers()
1943 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001944 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001945
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001946 def ChromiumSyncTransientErrorOpHandler(self):
1947 test_name = "/chromiumsync/transienterror"
1948 if not self._ShouldHandleRequest(test_name):
1949 return False
1950 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1951 self.send_response(result)
1952 self.send_header('Content-Type', 'text/html')
1953 self.send_header('Content-Length', len(raw_reply))
1954 self.end_headers()
1955 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001956 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001957
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001958 def ChromiumSyncErrorOpHandler(self):
1959 test_name = "/chromiumsync/error"
1960 if not self._ShouldHandleRequest(test_name):
1961 return False
1962 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1963 self.path)
1964 self.send_response(result)
1965 self.send_header('Content-Type', 'text/html')
1966 self.send_header('Content-Length', len(raw_reply))
1967 self.end_headers()
1968 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001969 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001970
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001971 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1972 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001973 if not self._ShouldHandleRequest(test_name):
1974 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001975 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001976 self.send_response(result)
1977 self.send_header('Content-Type', 'text/html')
1978 self.send_header('Content-Length', len(raw_reply))
1979 self.end_headers()
1980 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001981 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001982
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001983 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1984 test_name = "/chromiumsync/createsyncedbookmarks"
1985 if not self._ShouldHandleRequest(test_name):
1986 return False
1987 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1988 self.send_response(result)
1989 self.send_header('Content-Type', 'text/html')
1990 self.send_header('Content-Length', len(raw_reply))
1991 self.end_headers()
1992 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001993 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001994
akalin@chromium.org154bb132010-11-12 02:20:27 +00001995
newt@chromium.org1fc32742012-10-20 00:28:35 +00001996def MakeDataDir(options):
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001997 if options.data_dir:
1998 if not os.path.isdir(options.data_dir):
1999 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
2000 return None
2001 my_data_dir = options.data_dir
2002 else:
2003 # Create the default path to our data dir, relative to the exe dir.
2004 my_data_dir = os.path.dirname(sys.argv[0])
2005 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
2006 "test", "data")
2007
2008 #TODO(ibrar): Must use Find* funtion defined in google\tools
2009 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
2010
2011 return my_data_dir
2012
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002013class OCSPHandler(BasePageHandler):
2014 def __init__(self, request, client_address, socket_server):
2015 handlers = [self.OCSPResponse]
2016 self.ocsp_response = socket_server.ocsp_response
2017 BasePageHandler.__init__(self, request, client_address, socket_server,
2018 [], handlers, [], handlers, [])
2019
2020 def OCSPResponse(self):
2021 self.send_response(200)
2022 self.send_header('Content-Type', 'application/ocsp-response')
2023 self.send_header('Content-Length', str(len(self.ocsp_response)))
2024 self.end_headers()
2025
2026 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002027
2028class TCPEchoHandler(SocketServer.BaseRequestHandler):
2029 """The RequestHandler class for TCP echo server.
2030
2031 It is instantiated once per connection to the server, and overrides the
2032 handle() method to implement communication to the client.
2033 """
2034
2035 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002036 """Handles the request from the client and constructs a response."""
2037
2038 data = self.request.recv(65536).strip()
2039 # Verify the "echo request" message received from the client. Send back
2040 # "echo response" message if "echo request" message is valid.
2041 try:
2042 return_data = echo_message.GetEchoResponseData(data)
2043 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002044 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002045 except ValueError:
2046 return
2047
2048 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002049
2050
2051class UDPEchoHandler(SocketServer.BaseRequestHandler):
2052 """The RequestHandler class for UDP echo server.
2053
2054 It is instantiated once per connection to the server, and overrides the
2055 handle() method to implement communication to the client.
2056 """
2057
2058 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002059 """Handles the request from the client and constructs a response."""
2060
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002061 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002062 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002063 # Verify the "echo request" message received from the client. Send back
2064 # "echo response" message if "echo request" message is valid.
2065 try:
2066 return_data = echo_message.GetEchoResponseData(data)
2067 if not return_data:
2068 return
2069 except ValueError:
2070 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002071 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002072
2073
bashi@chromium.org33233532012-09-08 17:37:24 +00002074class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2075 """A request handler that behaves as a proxy server which requires
2076 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2077 """
2078
2079 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2080
2081 def parse_request(self):
2082 """Overrides parse_request to check credential."""
2083
2084 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2085 return False
2086
2087 auth = self.headers.getheader('Proxy-Authorization')
2088 if auth != self._AUTH_CREDENTIAL:
2089 self.send_response(407)
2090 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2091 self.end_headers()
2092 return False
2093
2094 return True
2095
2096 def _start_read_write(self, sock):
2097 sock.setblocking(0)
2098 self.request.setblocking(0)
2099 rlist = [self.request, sock]
2100 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002101 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002102 if errors:
2103 self.send_response(500)
2104 self.end_headers()
2105 return
2106 for s in ready_sockets:
2107 received = s.recv(1024)
2108 if len(received) == 0:
2109 return
2110 if s == self.request:
2111 other = sock
2112 else:
2113 other = self.request
2114 other.send(received)
2115
2116 def _do_common_method(self):
2117 url = urlparse.urlparse(self.path)
2118 port = url.port
2119 if not port:
2120 if url.scheme == 'http':
2121 port = 80
2122 elif url.scheme == 'https':
2123 port = 443
2124 if not url.hostname or not port:
2125 self.send_response(400)
2126 self.end_headers()
2127 return
2128
2129 if len(url.path) == 0:
2130 path = '/'
2131 else:
2132 path = url.path
2133 if len(url.query) > 0:
2134 path = '%s?%s' % (url.path, url.query)
2135
2136 sock = None
2137 try:
2138 sock = socket.create_connection((url.hostname, port))
2139 sock.send('%s %s %s\r\n' % (
2140 self.command, path, self.protocol_version))
2141 for header in self.headers.headers:
2142 header = header.strip()
2143 if (header.lower().startswith('connection') or
2144 header.lower().startswith('proxy')):
2145 continue
2146 sock.send('%s\r\n' % header)
2147 sock.send('\r\n')
2148 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002149 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002150 self.send_response(500)
2151 self.end_headers()
2152 finally:
2153 if sock is not None:
2154 sock.close()
2155
2156 def do_CONNECT(self):
2157 try:
2158 pos = self.path.rfind(':')
2159 host = self.path[:pos]
2160 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002161 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002162 self.send_response(400)
2163 self.end_headers()
2164
2165 try:
2166 sock = socket.create_connection((host, port))
2167 self.send_response(200, 'Connection established')
2168 self.end_headers()
2169 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002170 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002171 self.send_response(500)
2172 self.end_headers()
2173 finally:
2174 sock.close()
2175
2176 def do_GET(self):
2177 self._do_common_method()
2178
2179 def do_HEAD(self):
2180 self._do_common_method()
2181
2182
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002183class FileMultiplexer:
2184 def __init__(self, fd1, fd2) :
2185 self.__fd1 = fd1
2186 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002187
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002188 def __del__(self) :
newt@chromium.org1fc32742012-10-20 00:28:35 +00002189 self.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002190
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002191 def write(self, text) :
2192 self.__fd1.write(text)
2193 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002194
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002195 def flush(self) :
2196 self.__fd1.flush()
2197 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002198
newt@chromium.org1fc32742012-10-20 00:28:35 +00002199 def close(self):
2200 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2201 self.__fd1.close()
2202 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2203 self.__fd2.close()
2204
2205
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002206def main(options, _args):
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002207 logfile = open('testserver.log', 'w')
2208 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2209 if options.log_to_console:
2210 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2211 else:
2212 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002213
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002214 port = options.port
2215 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002216
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002217 server_data = {}
2218 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002219
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002220 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002221
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002222 if options.server_type == SERVER_HTTP:
2223 if options.https:
2224 pem_cert_and_key = None
2225 if options.cert_and_key_file:
2226 if not os.path.isfile(options.cert_and_key_file):
2227 print ('specified server cert file not found: ' +
2228 options.cert_and_key_file + ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002229 return 1
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002230 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002231 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002232 # generate a new certificate and run an OCSP server for it.
2233 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2234 print ('OCSP server started on %s:%d...' %
2235 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002236
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002237 ocsp_der = None
2238 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002239
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002240 if options.ocsp == 'ok':
2241 ocsp_state = minica.OCSP_STATE_GOOD
2242 elif options.ocsp == 'revoked':
2243 ocsp_state = minica.OCSP_STATE_REVOKED
2244 elif options.ocsp == 'invalid':
2245 ocsp_state = minica.OCSP_STATE_INVALID
2246 elif options.ocsp == 'unauthorized':
2247 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2248 elif options.ocsp == 'unknown':
2249 ocsp_state = minica.OCSP_STATE_UNKNOWN
2250 else:
2251 print 'unknown OCSP status: ' + options.ocsp_status
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002252 return 1
mattm@chromium.org07e28412012-09-05 00:19:41 +00002253
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002254 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2255 subject = "127.0.0.1",
2256 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server.server_port)),
2257 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002258
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002259 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002260
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002261 for ca_cert in options.ssl_client_ca:
2262 if not os.path.isfile(ca_cert):
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002263 print ('specified trusted client CA file not found: ' + ca_cert +
2264 ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002265 return 1
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002266 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2267 options.ssl_client_auth, options.ssl_client_ca,
2268 options.ssl_bulk_cipher, options.record_resume,
2269 options.tls_intolerant)
2270 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002271 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002272 server = HTTPServer((host, port), TestPageHandler)
2273 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002274
newt@chromium.org1fc32742012-10-20 00:28:35 +00002275 server.data_dir = MakeDataDir(options)
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002276 server.file_root_url = options.file_root_url
2277 server_data['port'] = server.server_port
2278 server._device_management_handler = None
2279 server.policy_keys = options.policy_keys
2280 server.policy_user = options.policy_user
2281 server.gdata_auth_token = options.auth_token
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002282 elif options.server_type == SERVER_WEBSOCKET:
2283 # Launch pywebsocket via WebSocketServer.
2284 logger = logging.getLogger()
2285 logger.addHandler(logging.StreamHandler())
2286 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2287 # is required to work correctly. It should be fixed from pywebsocket side.
newt@chromium.org1fc32742012-10-20 00:28:35 +00002288 os.chdir(MakeDataDir(options))
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002289 websocket_options = WebSocketOptions(host, port, '.')
2290 if options.cert_and_key_file:
2291 websocket_options.use_tls = True
2292 websocket_options.private_key = options.cert_and_key_file
2293 websocket_options.certificate = options.cert_and_key_file
2294 if options.ssl_client_auth:
2295 websocket_options.tls_client_auth = True
2296 if len(options.ssl_client_ca) != 1:
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002297 print 'one trusted client CA file should be specified'
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002298 return 1
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002299 if not os.path.isfile(options.ssl_client_ca[0]):
2300 print ('specified trusted client CA file not found: ' +
2301 options.ssl_client_ca[0] + ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002302 return 1
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002303 websocket_options.tls_client_ca = options.ssl_client_ca[0]
2304 server = WebSocketServer(websocket_options)
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002305 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2306 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002307 elif options.server_type == SERVER_SYNC:
2308 xmpp_port = options.xmpp_port
2309 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2310 print 'Sync HTTP server started on port %d...' % server.server_port
2311 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2312 server_data['port'] = server.server_port
2313 server_data['xmpp_port'] = server.xmpp_port
2314 elif options.server_type == SERVER_TCP_ECHO:
2315 # Used for generating the key (randomly) that encodes the "echo request"
2316 # message.
2317 random.seed()
2318 server = TCPEchoServer((host, port), TCPEchoHandler)
2319 print 'Echo TCP server started on port %d...' % server.server_port
2320 server_data['port'] = server.server_port
2321 elif options.server_type == SERVER_UDP_ECHO:
2322 # Used for generating the key (randomly) that encodes the "echo request"
2323 # message.
2324 random.seed()
2325 server = UDPEchoServer((host, port), UDPEchoHandler)
2326 print 'Echo UDP server started on port %d...' % server.server_port
2327 server_data['port'] = server.server_port
bashi@chromium.org33233532012-09-08 17:37:24 +00002328 elif options.server_type == SERVER_BASIC_AUTH_PROXY:
2329 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2330 print 'BasicAuthProxy server started on port %d...' % server.server_port
2331 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002332 # means FTP Server
2333 else:
newt@chromium.org1fc32742012-10-20 00:28:35 +00002334 my_data_dir = MakeDataDir(options)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002335
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002336 # Instantiate a dummy authorizer for managing 'virtual' users
2337 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002338
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002339 # Define a new user having full r/w permissions and a read-only
2340 # anonymous user
2341 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002342
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002343 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002344
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002345 # Instantiate FTP handler class
2346 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2347 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002348
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002349 # Define a customized banner (string returned when client connects)
2350 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2351 pyftpdlib.ftpserver.__ver__)
2352
2353 # Instantiate FTP server class and listen to address:port
2354 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2355 server_data['port'] = server.socket.getsockname()[1]
2356 print 'FTP server started on port %d...' % server_data['port']
2357
2358 # Notify the parent that we've started. (BaseServer subclasses
2359 # bind their sockets on construction.)
2360 if options.startup_pipe is not None:
2361 server_data_json = json.dumps(server_data)
2362 server_data_len = len(server_data_json)
2363 print 'sending server_data: %s (%d bytes)' % (
2364 server_data_json, server_data_len)
2365 if sys.platform == 'win32':
2366 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2367 else:
2368 fd = options.startup_pipe
2369 startup_pipe = os.fdopen(fd, "w")
2370 # First write the data length as an unsigned 4-byte value. This
2371 # is _not_ using network byte ordering since the other end of the
2372 # pipe is on the same machine.
2373 startup_pipe.write(struct.pack('=L', server_data_len))
2374 startup_pipe.write(server_data_json)
2375 startup_pipe.close()
2376
2377 if ocsp_server is not None:
2378 ocsp_server.serve_forever_on_thread()
2379
2380 try:
2381 server.serve_forever()
2382 except KeyboardInterrupt:
2383 print 'shutting down server'
2384 if ocsp_server is not None:
2385 ocsp_server.stop_serving()
2386 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002387
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002388 return 0
2389
initial.commit94958cf2008-07-26 22:42:52 +00002390if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002391 option_parser = optparse.OptionParser()
2392 option_parser.add_option("-f", '--ftp', action='store_const',
2393 const=SERVER_FTP, default=SERVER_HTTP,
2394 dest='server_type',
2395 help='start up an FTP server.')
2396 option_parser.add_option('', '--sync', action='store_const',
2397 const=SERVER_SYNC, default=SERVER_HTTP,
2398 dest='server_type',
2399 help='start up a sync server.')
2400 option_parser.add_option('', '--tcp-echo', action='store_const',
2401 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2402 dest='server_type',
2403 help='start up a tcp echo server.')
2404 option_parser.add_option('', '--udp-echo', action='store_const',
2405 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2406 dest='server_type',
2407 help='start up a udp echo server.')
bashi@chromium.org33233532012-09-08 17:37:24 +00002408 option_parser.add_option('', '--basic-auth-proxy', action='store_const',
2409 const=SERVER_BASIC_AUTH_PROXY, default=SERVER_HTTP,
2410 dest='server_type',
2411 help='start up a proxy server which requires basic '
2412 'authentication.')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002413 option_parser.add_option('', '--websocket', action='store_const',
2414 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2415 dest='server_type',
2416 help='start up a WebSocket server.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002417 option_parser.add_option('', '--log-to-console', action='store_const',
2418 const=True, default=False,
2419 dest='log_to_console',
2420 help='Enables or disables sys.stdout logging to '
2421 'the console.')
2422 option_parser.add_option('', '--port', default='0', type='int',
2423 help='Port used by the server. If unspecified, the '
2424 'server will listen on an ephemeral port.')
2425 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2426 help='Port used by the XMPP server. If unspecified, '
2427 'the XMPP server will listen on an ephemeral port.')
2428 option_parser.add_option('', '--data-dir', dest='data_dir',
2429 help='Directory from which to read the files.')
2430 option_parser.add_option('', '--https', action='store_true', dest='https',
2431 help='Specify that https should be used.')
2432 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2433 help='specify the path to the file containing the '
2434 'certificate and private key for the server in PEM '
2435 'format')
2436 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2437 help='The type of OCSP response generated for the '
2438 'automatically generated certificate. One of '
2439 '[ok,revoked,invalid]')
2440 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2441 default='0', type='int',
2442 help='If nonzero, certain TLS connections will be'
2443 ' aborted in order to test version fallback. 1'
2444 ' means all TLS versions will be aborted. 2 means'
2445 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2446 ' 1.2 or higher will be aborted.')
2447 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2448 const=True, default=False, action='store_const',
2449 help='Record resumption cache events rather than'
2450 ' resuming as normal. Allows the use of the'
2451 ' /ssl-session-cache request')
2452 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2453 help='Require SSL client auth on every connection.')
2454 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2455 help='Specify that the client certificate request '
2456 'should include the CA named in the subject of '
2457 'the DER-encoded certificate contained in the '
2458 'specified file. This option may appear multiple '
2459 'times, indicating multiple CA names should be '
2460 'sent in the request.')
2461 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2462 help='Specify the bulk encryption algorithm(s)'
2463 'that will be accepted by the SSL server. Valid '
2464 'values are "aes256", "aes128", "3des", "rc4". If '
2465 'omitted, all algorithms will be used. This '
2466 'option may appear multiple times, indicating '
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002467 'multiple algorithms should be enabled.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002468 option_parser.add_option('', '--file-root-url', default='/files/',
2469 help='Specify a root URL for files served.')
2470 option_parser.add_option('', '--startup-pipe', type='int',
2471 dest='startup_pipe',
2472 help='File handle of pipe to parent process')
2473 option_parser.add_option('', '--policy-key', action='append',
2474 dest='policy_keys',
2475 help='Specify a path to a PEM-encoded private key '
2476 'to use for policy signing. May be specified '
2477 'multiple times in order to load multipe keys into '
2478 'the server. If ther server has multiple keys, it '
2479 'will rotate through them in at each request a '
2480 'round-robin fashion. The server will generate a '
2481 'random key if none is specified on the command '
2482 'line.')
2483 option_parser.add_option('', '--policy-user', default='user@example.com',
2484 dest='policy_user',
2485 help='Specify the user name the server should '
2486 'report back to the client as the user owning the '
2487 'token used for making the policy request.')
2488 option_parser.add_option('', '--host', default='127.0.0.1',
2489 dest='host',
2490 help='Hostname or IP upon which the server will '
2491 'listen. Client connections will also only be '
2492 'allowed from this address.')
2493 option_parser.add_option('', '--auth-token', dest='auth_token',
2494 help='Specify the auth token which should be used'
2495 'in the authorization header for GData.')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002496 main_options, main_args = option_parser.parse_args()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002497
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002498 sys.exit(main(main_options, main_args))
2499