blob: e02de0863f54431c0a623517533b745d60983b73 [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
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000042import echo_message
toyoshim@chromium.org716d0572012-10-16 13:52:05 +000043
44# TODO(toyoshim): Some try bots for pyauto don't check out pywebsocket repos
45# unexpectedly. pyauto doesn't use WebSocket module, so just ignore
46# ImportError as a temporal workaround.
47# http://crbug.com/155918
48try:
49 from mod_pywebsocket.standalone import WebSocketServer
50except ImportError:
51 pass
52
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000053import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000054import tlslite
55import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000056
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000057if sys.platform == 'win32':
58 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000059
maruel@chromium.org756cf982009-03-05 12:46:38 +000060SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000061SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000062SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000063SERVER_TCP_ECHO = 3
64SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000065SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000066SERVER_WEBSOCKET = 6
67
68# Default request queue size for WebSocketServer.
69_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000070
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000071# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000072debug_output = sys.stderr
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +000073def debug(string):
74 debug_output.write(string + "\n")
initial.commit94958cf2008-07-26 22:42:52 +000075 debug_output.flush()
76
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000077class WebSocketOptions:
78 """Holds options for WebSocketServer."""
79
80 def __init__(self, host, port, data_dir):
81 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
82 self.server_host = host
83 self.port = port
84 self.websock_handlers = data_dir
85 self.scan_dir = None
86 self.allow_handlers_outside_root_dir = False
87 self.websock_handlers_map_file = None
88 self.cgi_directories = []
89 self.is_executable_method = None
90 self.allow_draft75 = False
91 self.strict = True
92
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000093 self.use_tls = False
94 self.private_key = None
95 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000096 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000097 self.tls_client_ca = None
98 self.use_basic_auth = False
99
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000100class RecordingSSLSessionCache(object):
101 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
102 lookups and inserts in order to test session cache behaviours."""
103
104 def __init__(self):
105 self.log = []
106
107 def __getitem__(self, sessionID):
108 self.log.append(('lookup', sessionID))
109 raise KeyError()
110
111 def __setitem__(self, sessionID, session):
112 self.log.append(('insert', sessionID))
113
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000114
115class ClientRestrictingServerMixIn:
116 """Implements verify_request to limit connections to our configured IP
117 address."""
118
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000119 def verify_request(self, _request, client_address):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000120 return client_address[0] == self.server_address[0]
121
122
initial.commit94958cf2008-07-26 22:42:52 +0000123class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000124 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000125 to be exited cleanly (by setting its "stop" member to True)."""
126
127 def serve_forever(self):
128 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000129 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000130 while not self.stop:
131 self.handle_request()
132 self.socket.close()
133
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000134
135class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000136 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000137 verification."""
138
139 pass
140
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000141class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
142 """This is a specialization of HTTPServer that serves an
143 OCSP response"""
144
145 def serve_forever_on_thread(self):
146 self.thread = threading.Thread(target = self.serve_forever,
147 name = "OCSPServerThread")
148 self.thread.start()
149
150 def stop_serving(self):
151 self.shutdown()
152 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000153
154class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
155 ClientRestrictingServerMixIn,
156 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000157 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000158 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000159
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000160 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000161 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000162 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000163 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
164 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000165 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000166 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000167 self.tls_intolerant = tls_intolerant
168
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000169 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000170 s = open(ca_file).read()
171 x509 = tlslite.api.X509()
172 x509.parse(s)
173 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000174 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
175 if ssl_bulk_ciphers is not None:
176 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000177
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000178 if record_resume_info:
179 # If record_resume_info is true then we'll replace the session cache with
180 # an object that records the lookups and inserts that it sees.
181 self.session_cache = RecordingSSLSessionCache()
182 else:
183 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000184 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
185
186 def handshake(self, tlsConnection):
187 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000188
initial.commit94958cf2008-07-26 22:42:52 +0000189 try:
190 tlsConnection.handshakeServer(certChain=self.cert_chain,
191 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000192 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000193 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000194 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000195 reqCAs=self.ssl_client_cas,
196 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000197 tlsConnection.ignoreAbruptClose = True
198 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000199 except tlslite.api.TLSAbruptCloseError:
200 # Ignore abrupt close.
201 return True
initial.commit94958cf2008-07-26 22:42:52 +0000202 except tlslite.api.TLSError, error:
203 print "Handshake failure:", str(error)
204 return False
205
akalin@chromium.org154bb132010-11-12 02:20:27 +0000206
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000207class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000208 """An HTTP server that handles sync commands."""
209
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000210 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000211 # We import here to avoid pulling in chromiumsync's dependencies
212 # unless strictly necessary.
213 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000214 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000215 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000216 self._sync_handler = chromiumsync.TestServer()
217 self._xmpp_socket_map = {}
218 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000219 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000220 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000221 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000222
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000223 def GetXmppServer(self):
224 return self._xmpp_server
225
akalin@chromium.org154bb132010-11-12 02:20:27 +0000226 def HandleCommand(self, query, raw_request):
227 return self._sync_handler.HandleCommand(query, raw_request)
228
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000229 def HandleRequestNoBlock(self):
230 """Handles a single request.
231
232 Copied from SocketServer._handle_request_noblock().
233 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000234
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000235 try:
236 request, client_address = self.get_request()
237 except socket.error:
238 return
239 if self.verify_request(request, client_address):
240 try:
241 self.process_request(request, client_address)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000242 except Exception:
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000243 self.handle_error(request, client_address)
244 self.close_request(request)
245
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000246 def SetAuthenticated(self, auth_valid):
247 self.authenticated = auth_valid
248
249 def GetAuthenticated(self):
250 return self.authenticated
251
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000252 def serve_forever(self):
253 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
254 """
255
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000256 def HandleXmppSocket(fd, socket_map, handler):
257 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000258
259 Adapted from asyncore.read() et al.
260 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000261
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000262 xmpp_connection = socket_map.get(fd)
263 # This could happen if a previous handler call caused fd to get
264 # removed from socket_map.
265 if xmpp_connection is None:
266 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000267 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000268 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000269 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
270 raise
271 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000272 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000273
274 while True:
275 read_fds = [ self.fileno() ]
276 write_fds = []
277 exceptional_fds = []
278
279 for fd, xmpp_connection in self._xmpp_socket_map.items():
280 is_r = xmpp_connection.readable()
281 is_w = xmpp_connection.writable()
282 if is_r:
283 read_fds.append(fd)
284 if is_w:
285 write_fds.append(fd)
286 if is_r or is_w:
287 exceptional_fds.append(fd)
288
289 try:
290 read_fds, write_fds, exceptional_fds = (
291 select.select(read_fds, write_fds, exceptional_fds))
292 except select.error, err:
293 if err.args[0] != errno.EINTR:
294 raise
295 else:
296 continue
297
298 for fd in read_fds:
299 if fd == self.fileno():
300 self.HandleRequestNoBlock()
301 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000302 HandleXmppSocket(fd, self._xmpp_socket_map,
303 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000304
305 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000306 HandleXmppSocket(fd, self._xmpp_socket_map,
307 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000308
309 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000310 HandleXmppSocket(fd, self._xmpp_socket_map,
311 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000312
akalin@chromium.org154bb132010-11-12 02:20:27 +0000313
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000314class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
315 """This is a specialization of FTPServer that adds client verification."""
316
317 pass
318
319
320class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000321 """A TCP echo server that echoes back what it has received."""
322
323 def server_bind(self):
324 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000325
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000326 SocketServer.TCPServer.server_bind(self)
327 host, port = self.socket.getsockname()[:2]
328 self.server_name = socket.getfqdn(host)
329 self.server_port = port
330
331 def serve_forever(self):
332 self.stop = False
333 self.nonce_time = None
334 while not self.stop:
335 self.handle_request()
336 self.socket.close()
337
338
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000339class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000340 """A UDP echo server that echoes back what it has received."""
341
342 def server_bind(self):
343 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000344
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000345 SocketServer.UDPServer.server_bind(self)
346 host, port = self.socket.getsockname()[:2]
347 self.server_name = socket.getfqdn(host)
348 self.server_port = port
349
350 def serve_forever(self):
351 self.stop = False
352 self.nonce_time = None
353 while not self.stop:
354 self.handle_request()
355 self.socket.close()
356
357
akalin@chromium.org154bb132010-11-12 02:20:27 +0000358class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
359
360 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000361 connect_handlers, get_handlers, head_handlers, post_handlers,
362 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000363 self._connect_handlers = connect_handlers
364 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000365 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000366 self._post_handlers = post_handlers
367 self._put_handlers = put_handlers
368 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
369 self, request, client_address, socket_server)
370
371 def log_request(self, *args, **kwargs):
372 # Disable request logging to declutter test log output.
373 pass
374
375 def _ShouldHandleRequest(self, handler_name):
376 """Determines if the path can be handled by the handler.
377
378 We consider a handler valid if the path begins with the
379 handler name. It can optionally be followed by "?*", "/*".
380 """
381
382 pattern = re.compile('%s($|\?|/).*' % handler_name)
383 return pattern.match(self.path)
384
385 def do_CONNECT(self):
386 for handler in self._connect_handlers:
387 if handler():
388 return
389
390 def do_GET(self):
391 for handler in self._get_handlers:
392 if handler():
393 return
394
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000395 def do_HEAD(self):
396 for handler in self._head_handlers:
397 if handler():
398 return
399
akalin@chromium.org154bb132010-11-12 02:20:27 +0000400 def do_POST(self):
401 for handler in self._post_handlers:
402 if handler():
403 return
404
405 def do_PUT(self):
406 for handler in self._put_handlers:
407 if handler():
408 return
409
410
411class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000412
413 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000414 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000415 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000416 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000417 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000418 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000419 self.NoCacheMaxAgeTimeHandler,
420 self.NoCacheTimeHandler,
421 self.CacheTimeHandler,
422 self.CacheExpiresHandler,
423 self.CacheProxyRevalidateHandler,
424 self.CachePrivateHandler,
425 self.CachePublicHandler,
426 self.CacheSMaxAgeHandler,
427 self.CacheMustRevalidateHandler,
428 self.CacheMustRevalidateMaxAgeHandler,
429 self.CacheNoStoreHandler,
430 self.CacheNoStoreMaxAgeHandler,
431 self.CacheNoTransformHandler,
432 self.DownloadHandler,
433 self.DownloadFinishHandler,
434 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000435 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000436 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000437 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000438 self.GDataAuthHandler,
439 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000441 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000442 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000443 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000444 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000445 self.AuthBasicHandler,
446 self.AuthDigestHandler,
447 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000448 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000449 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000450 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000451 self.ServerRedirectHandler,
452 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000453 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000454 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000455 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000456 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000457 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000458 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000459 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000460 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000461 self.DeviceManagementHandler,
462 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000463 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000464 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000465 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000466 head_handlers = [
467 self.FileHandler,
468 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000469
maruel@google.come250a9b2009-03-10 17:39:46 +0000470 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000471 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000472 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000473 'gif': 'image/gif',
474 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000475 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000476 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000477 'pdf' : 'application/pdf',
478 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000479 }
initial.commit94958cf2008-07-26 22:42:52 +0000480 self._default_mime_type = 'text/html'
481
akalin@chromium.org154bb132010-11-12 02:20:27 +0000482 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000483 connect_handlers, get_handlers, head_handlers,
484 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000485
initial.commit94958cf2008-07-26 22:42:52 +0000486 def GetMIMETypeFromName(self, file_name):
487 """Returns the mime type for the specified file_name. So far it only looks
488 at the file extension."""
489
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000490 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000491 if len(extension) == 0:
492 # no extension.
493 return self._default_mime_type
494
ericroman@google.comc17ca532009-05-07 03:51:05 +0000495 # extension starts with a dot, so we need to remove it
496 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000497
initial.commit94958cf2008-07-26 22:42:52 +0000498 def NoCacheMaxAgeTimeHandler(self):
499 """This request handler yields a page with the title set to the current
500 system time, and no caching requested."""
501
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000502 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000503 return False
504
505 self.send_response(200)
506 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000507 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000508 self.end_headers()
509
maruel@google.come250a9b2009-03-10 17:39:46 +0000510 self.wfile.write('<html><head><title>%s</title></head></html>' %
511 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000512
513 return True
514
515 def NoCacheTimeHandler(self):
516 """This request handler yields a page with the title set to the current
517 system time, and no caching requested."""
518
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000519 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000520 return False
521
522 self.send_response(200)
523 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000524 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000525 self.end_headers()
526
maruel@google.come250a9b2009-03-10 17:39:46 +0000527 self.wfile.write('<html><head><title>%s</title></head></html>' %
528 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000529
530 return True
531
532 def CacheTimeHandler(self):
533 """This request handler yields a page with the title set to the current
534 system time, and allows caching for one minute."""
535
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000536 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000537 return False
538
539 self.send_response(200)
540 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000541 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000542 self.end_headers()
543
maruel@google.come250a9b2009-03-10 17:39:46 +0000544 self.wfile.write('<html><head><title>%s</title></head></html>' %
545 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000546
547 return True
548
549 def CacheExpiresHandler(self):
550 """This request handler yields a page with the title set to the current
551 system time, and set the page to expire on 1 Jan 2099."""
552
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000553 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000554 return False
555
556 self.send_response(200)
557 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000558 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000559 self.end_headers()
560
maruel@google.come250a9b2009-03-10 17:39:46 +0000561 self.wfile.write('<html><head><title>%s</title></head></html>' %
562 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000563
564 return True
565
566 def CacheProxyRevalidateHandler(self):
567 """This request handler yields a page with the title set to the current
568 system time, and allows caching for 60 seconds"""
569
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000570 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000571 return False
572
573 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000574 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000575 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
576 self.end_headers()
577
maruel@google.come250a9b2009-03-10 17:39:46 +0000578 self.wfile.write('<html><head><title>%s</title></head></html>' %
579 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000580
581 return True
582
583 def CachePrivateHandler(self):
584 """This request handler yields a page with the title set to the current
585 system time, and allows caching for 5 seconds."""
586
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000587 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000588 return False
589
590 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000591 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000592 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000593 self.end_headers()
594
maruel@google.come250a9b2009-03-10 17:39:46 +0000595 self.wfile.write('<html><head><title>%s</title></head></html>' %
596 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000597
598 return True
599
600 def CachePublicHandler(self):
601 """This request handler yields a page with the title set to the current
602 system time, and allows caching for 5 seconds."""
603
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000604 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000605 return False
606
607 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000608 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000609 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000610 self.end_headers()
611
maruel@google.come250a9b2009-03-10 17:39:46 +0000612 self.wfile.write('<html><head><title>%s</title></head></html>' %
613 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000614
615 return True
616
617 def CacheSMaxAgeHandler(self):
618 """This request handler yields a page with the title set to the current
619 system time, and does not allow for caching."""
620
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000621 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000622 return False
623
624 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000625 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000626 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
627 self.end_headers()
628
maruel@google.come250a9b2009-03-10 17:39:46 +0000629 self.wfile.write('<html><head><title>%s</title></head></html>' %
630 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000631
632 return True
633
634 def CacheMustRevalidateHandler(self):
635 """This request handler yields a page with the title set to the current
636 system time, and does not allow caching."""
637
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000638 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000639 return False
640
641 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000642 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000643 self.send_header('Cache-Control', 'must-revalidate')
644 self.end_headers()
645
maruel@google.come250a9b2009-03-10 17:39:46 +0000646 self.wfile.write('<html><head><title>%s</title></head></html>' %
647 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000648
649 return True
650
651 def CacheMustRevalidateMaxAgeHandler(self):
652 """This request handler yields a page with the title set to the current
653 system time, and does not allow caching event though max-age of 60
654 seconds is specified."""
655
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000656 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000657 return False
658
659 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000660 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000661 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
662 self.end_headers()
663
maruel@google.come250a9b2009-03-10 17:39:46 +0000664 self.wfile.write('<html><head><title>%s</title></head></html>' %
665 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000666
667 return True
668
initial.commit94958cf2008-07-26 22:42:52 +0000669 def CacheNoStoreHandler(self):
670 """This request handler yields a page with the title set to the current
671 system time, and does not allow the page to be stored."""
672
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000673 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000674 return False
675
676 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000677 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000678 self.send_header('Cache-Control', 'no-store')
679 self.end_headers()
680
maruel@google.come250a9b2009-03-10 17:39:46 +0000681 self.wfile.write('<html><head><title>%s</title></head></html>' %
682 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000683
684 return True
685
686 def CacheNoStoreMaxAgeHandler(self):
687 """This request handler yields a page with the title set to the current
688 system time, and does not allow the page to be stored even though max-age
689 of 60 seconds is specified."""
690
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000691 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000692 return False
693
694 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000695 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000696 self.send_header('Cache-Control', 'max-age=60, no-store')
697 self.end_headers()
698
maruel@google.come250a9b2009-03-10 17:39:46 +0000699 self.wfile.write('<html><head><title>%s</title></head></html>' %
700 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000701
702 return True
703
704
705 def CacheNoTransformHandler(self):
706 """This request handler yields a page with the title set to the current
707 system time, and does not allow the content to transformed during
708 user-agent caching"""
709
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000710 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000711 return False
712
713 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000714 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000715 self.send_header('Cache-Control', 'no-transform')
716 self.end_headers()
717
maruel@google.come250a9b2009-03-10 17:39:46 +0000718 self.wfile.write('<html><head><title>%s</title></head></html>' %
719 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000720
721 return True
722
723 def EchoHeader(self):
724 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000725
ananta@chromium.org219b2062009-10-23 16:09:41 +0000726 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000727
ananta@chromium.org56812d02011-04-07 17:52:05 +0000728 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000729 """This function echoes back the value of a specific request header while
730 allowing caching for 16 hours."""
731
ananta@chromium.org56812d02011-04-07 17:52:05 +0000732 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000733
734 def EchoHeaderHelper(self, echo_header):
735 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000736
ananta@chromium.org219b2062009-10-23 16:09:41 +0000737 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000738 return False
739
740 query_char = self.path.find('?')
741 if query_char != -1:
742 header_name = self.path[query_char+1:]
743
744 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000745 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000746 if echo_header == '/echoheadercache':
747 self.send_header('Cache-control', 'max-age=60000')
748 else:
749 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000750 # insert a vary header to properly indicate that the cachability of this
751 # request is subject to value of the request header being echoed.
752 if len(header_name) > 0:
753 self.send_header('Vary', header_name)
754 self.end_headers()
755
756 if len(header_name) > 0:
757 self.wfile.write(self.headers.getheader(header_name))
758
759 return True
760
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000761 def ReadRequestBody(self):
762 """This function reads the body of the current HTTP request, handling
763 both plain and chunked transfer encoded requests."""
764
765 if self.headers.getheader('transfer-encoding') != 'chunked':
766 length = int(self.headers.getheader('content-length'))
767 return self.rfile.read(length)
768
769 # Read the request body as chunks.
770 body = ""
771 while True:
772 line = self.rfile.readline()
773 length = int(line, 16)
774 if length == 0:
775 self.rfile.readline()
776 break
777 body += self.rfile.read(length)
778 self.rfile.read(2)
779 return body
780
initial.commit94958cf2008-07-26 22:42:52 +0000781 def EchoHandler(self):
782 """This handler just echoes back the payload of the request, for testing
783 form submission."""
784
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000785 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000786 return False
787
788 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000789 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000790 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000791 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000792 return True
793
794 def EchoTitleHandler(self):
795 """This handler is like Echo, but sets the page title to the request."""
796
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000797 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000798 return False
799
800 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000801 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000802 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000803 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000804 self.wfile.write('<html><head><title>')
805 self.wfile.write(request)
806 self.wfile.write('</title></head></html>')
807 return True
808
809 def EchoAllHandler(self):
810 """This handler yields a (more) human-readable page listing information
811 about the request header & contents."""
812
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000813 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000814 return False
815
816 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000817 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000818 self.end_headers()
819 self.wfile.write('<html><head><style>'
820 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
821 '</style></head><body>'
822 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000823 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000824 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000825
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000826 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000827 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000828 params = cgi.parse_qs(qs, keep_blank_values=1)
829
830 for param in params:
831 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000832
833 self.wfile.write('</pre>')
834
835 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
836
837 self.wfile.write('</body></html>')
838 return True
839
840 def DownloadHandler(self):
841 """This handler sends a downloadable file with or without reporting
842 the size (6K)."""
843
844 if self.path.startswith("/download-unknown-size"):
845 send_length = False
846 elif self.path.startswith("/download-known-size"):
847 send_length = True
848 else:
849 return False
850
851 #
852 # The test which uses this functionality is attempting to send
853 # small chunks of data to the client. Use a fairly large buffer
854 # so that we'll fill chrome's IO buffer enough to force it to
855 # actually write the data.
856 # See also the comments in the client-side of this test in
857 # download_uitest.cc
858 #
859 size_chunk1 = 35*1024
860 size_chunk2 = 10*1024
861
862 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000863 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000864 self.send_header('Cache-Control', 'max-age=0')
865 if send_length:
866 self.send_header('Content-Length', size_chunk1 + size_chunk2)
867 self.end_headers()
868
869 # First chunk of data:
870 self.wfile.write("*" * size_chunk1)
871 self.wfile.flush()
872
873 # handle requests until one of them clears this flag.
874 self.server.waitForDownload = True
875 while self.server.waitForDownload:
876 self.server.handle_request()
877
878 # Second chunk of data:
879 self.wfile.write("*" * size_chunk2)
880 return True
881
882 def DownloadFinishHandler(self):
883 """This handler just tells the server to finish the current download."""
884
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000885 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000886 return False
887
888 self.server.waitForDownload = False
889 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000890 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000891 self.send_header('Cache-Control', 'max-age=0')
892 self.end_headers()
893 return True
894
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000895 def _ReplaceFileData(self, data, query_parameters):
896 """Replaces matching substrings in a file.
897
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000898 If the 'replace_text' URL query parameter is present, it is expected to be
899 of the form old_text:new_text, which indicates that any old_text strings in
900 the file are replaced with new_text. Multiple 'replace_text' parameters may
901 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000902
903 If the parameters are not present, |data| is returned.
904 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000905
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000906 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000907 replace_text_values = query_dict.get('replace_text', [])
908 for replace_text_value in replace_text_values:
909 replace_text_args = replace_text_value.split(':')
910 if len(replace_text_args) != 2:
911 raise ValueError(
912 'replace_text must be of form old_text:new_text. Actual value: %s' %
913 replace_text_value)
914 old_text_b64, new_text_b64 = replace_text_args
915 old_text = base64.urlsafe_b64decode(old_text_b64)
916 new_text = base64.urlsafe_b64decode(new_text_b64)
917 data = data.replace(old_text, new_text)
918 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000919
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000920 def ZipFileHandler(self):
921 """This handler sends the contents of the requested file in compressed form.
922 Can pass in a parameter that specifies that the content length be
923 C - the compressed size (OK),
924 U - the uncompressed size (Non-standard, but handled),
925 S - less than compressed (OK because we keep going),
926 M - larger than compressed but less than uncompressed (an error),
927 L - larger than uncompressed (an error)
928 Example: compressedfiles/Picture_1.doc?C
929 """
930
931 prefix = "/compressedfiles/"
932 if not self.path.startswith(prefix):
933 return False
934
935 # Consume a request body if present.
936 if self.command == 'POST' or self.command == 'PUT' :
937 self.ReadRequestBody()
938
939 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
940
941 if not query in ('C', 'U', 'S', 'M', 'L'):
942 return False
943
944 sub_path = url_path[len(prefix):]
945 entries = sub_path.split('/')
946 file_path = os.path.join(self.server.data_dir, *entries)
947 if os.path.isdir(file_path):
948 file_path = os.path.join(file_path, 'index.html')
949
950 if not os.path.isfile(file_path):
951 print "File not found " + sub_path + " full path:" + file_path
952 self.send_error(404)
953 return True
954
955 f = open(file_path, "rb")
956 data = f.read()
957 uncompressed_len = len(data)
958 f.close()
959
960 # Compress the data.
961 data = zlib.compress(data)
962 compressed_len = len(data)
963
964 content_length = compressed_len
965 if query == 'U':
966 content_length = uncompressed_len
967 elif query == 'S':
968 content_length = compressed_len / 2
969 elif query == 'M':
970 content_length = (compressed_len + uncompressed_len) / 2
971 elif query == 'L':
972 content_length = compressed_len + uncompressed_len
973
974 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000975 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000976 self.send_header('Content-encoding', 'deflate')
977 self.send_header('Connection', 'close')
978 self.send_header('Content-Length', content_length)
979 self.send_header('ETag', '\'' + file_path + '\'')
980 self.end_headers()
981
982 self.wfile.write(data)
983
984 return True
985
initial.commit94958cf2008-07-26 22:42:52 +0000986 def FileHandler(self):
987 """This handler sends the contents of the requested file. Wow, it's like
988 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000989
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000990 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000991 if not self.path.startswith(prefix):
992 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000993 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000994
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000995 def PostOnlyFileHandler(self):
996 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000997
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000998 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000999 if not self.path.startswith(prefix):
1000 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00001001 return self._FileHandlerHelper(prefix)
1002
1003 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001004 request_body = ''
1005 if self.command == 'POST' or self.command == 'PUT':
1006 # Consume a request body if present.
1007 request_body = self.ReadRequestBody()
1008
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001009 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001010 query_dict = cgi.parse_qs(query)
1011
1012 expected_body = query_dict.get('expected_body', [])
1013 if expected_body and request_body not in expected_body:
1014 self.send_response(404)
1015 self.end_headers()
1016 self.wfile.write('')
1017 return True
1018
1019 expected_headers = query_dict.get('expected_headers', [])
1020 for expected_header in expected_headers:
1021 header_name, expected_value = expected_header.split(':')
1022 if self.headers.getheader(header_name) != expected_value:
1023 self.send_response(404)
1024 self.end_headers()
1025 self.wfile.write('')
1026 return True
1027
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001028 sub_path = url_path[len(prefix):]
1029 entries = sub_path.split('/')
1030 file_path = os.path.join(self.server.data_dir, *entries)
1031 if os.path.isdir(file_path):
1032 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001033
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001034 if not os.path.isfile(file_path):
1035 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001036 self.send_error(404)
1037 return True
1038
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001039 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001040 data = f.read()
1041 f.close()
1042
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001043 data = self._ReplaceFileData(data, query)
1044
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001045 old_protocol_version = self.protocol_version
1046
initial.commit94958cf2008-07-26 22:42:52 +00001047 # If file.mock-http-headers exists, it contains the headers we
1048 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001049 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001050 if os.path.isfile(headers_path):
1051 f = open(headers_path, "r")
1052
1053 # "HTTP/1.1 200 OK"
1054 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001055 http_major, http_minor, status_code = re.findall(
1056 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1057 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001058 self.send_response(int(status_code))
1059
1060 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001061 header_values = re.findall('(\S+):\s*(.*)', line)
1062 if len(header_values) > 0:
1063 # "name: value"
1064 name, value = header_values[0]
1065 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001066 f.close()
1067 else:
1068 # Could be more generic once we support mime-type sniffing, but for
1069 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001070
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001071 range_header = self.headers.get('Range')
1072 if range_header and range_header.startswith('bytes='):
1073 # Note this doesn't handle all valid byte range_header values (i.e.
1074 # left open ended ones), just enough for what we needed so far.
1075 range_header = range_header[6:].split('-')
1076 start = int(range_header[0])
1077 if range_header[1]:
1078 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001079 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001080 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001081
1082 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001083 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1084 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001085 self.send_header('Content-Range', content_range)
1086 data = data[start: end + 1]
1087 else:
1088 self.send_response(200)
1089
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001090 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001091 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001092 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001093 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001094 self.end_headers()
1095
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001096 if (self.command != 'HEAD'):
1097 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001098
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001099 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001100 return True
1101
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001102 def SetCookieHandler(self):
1103 """This handler just sets a cookie, for testing cookie handling."""
1104
1105 if not self._ShouldHandleRequest("/set-cookie"):
1106 return False
1107
1108 query_char = self.path.find('?')
1109 if query_char != -1:
1110 cookie_values = self.path[query_char + 1:].split('&')
1111 else:
1112 cookie_values = ("",)
1113 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001114 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001115 for cookie_value in cookie_values:
1116 self.send_header('Set-Cookie', '%s' % cookie_value)
1117 self.end_headers()
1118 for cookie_value in cookie_values:
1119 self.wfile.write('%s' % cookie_value)
1120 return True
1121
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001122 def SetManyCookiesHandler(self):
1123 """This handler just sets a given number of cookies, for testing handling
1124 of large numbers of cookies."""
1125
1126 if not self._ShouldHandleRequest("/set-many-cookies"):
1127 return False
1128
1129 query_char = self.path.find('?')
1130 if query_char != -1:
1131 num_cookies = int(self.path[query_char + 1:])
1132 else:
1133 num_cookies = 0
1134 self.send_response(200)
1135 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001136 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001137 self.send_header('Set-Cookie', 'a=')
1138 self.end_headers()
1139 self.wfile.write('%d cookies were sent' % num_cookies)
1140 return True
1141
mattm@chromium.org983fc462012-06-30 00:52:08 +00001142 def ExpectAndSetCookieHandler(self):
1143 """Expects some cookies to be sent, and if they are, sets more cookies.
1144
1145 The expect parameter specifies a required cookie. May be specified multiple
1146 times.
1147 The set parameter specifies a cookie to set if all required cookies are
1148 preset. May be specified multiple times.
1149 The data parameter specifies the response body data to be returned."""
1150
1151 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1152 return False
1153
1154 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1155 query_dict = cgi.parse_qs(query)
1156 cookies = set()
1157 if 'Cookie' in self.headers:
1158 cookie_header = self.headers.getheader('Cookie')
1159 cookies.update([s.strip() for s in cookie_header.split(';')])
1160 got_all_expected_cookies = True
1161 for expected_cookie in query_dict.get('expect', []):
1162 if expected_cookie not in cookies:
1163 got_all_expected_cookies = False
1164 self.send_response(200)
1165 self.send_header('Content-Type', 'text/html')
1166 if got_all_expected_cookies:
1167 for cookie_value in query_dict.get('set', []):
1168 self.send_header('Set-Cookie', '%s' % cookie_value)
1169 self.end_headers()
1170 for data_value in query_dict.get('data', []):
1171 self.wfile.write(data_value)
1172 return True
1173
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001174 def SetHeaderHandler(self):
1175 """This handler sets a response header. Parameters are in the
1176 key%3A%20value&key2%3A%20value2 format."""
1177
1178 if not self._ShouldHandleRequest("/set-header"):
1179 return False
1180
1181 query_char = self.path.find('?')
1182 if query_char != -1:
1183 headers_values = self.path[query_char + 1:].split('&')
1184 else:
1185 headers_values = ("",)
1186 self.send_response(200)
1187 self.send_header('Content-Type', 'text/html')
1188 for header_value in headers_values:
1189 header_value = urllib.unquote(header_value)
1190 (key, value) = header_value.split(': ', 1)
1191 self.send_header(key, value)
1192 self.end_headers()
1193 for header_value in headers_values:
1194 self.wfile.write('%s' % header_value)
1195 return True
1196
initial.commit94958cf2008-07-26 22:42:52 +00001197 def AuthBasicHandler(self):
1198 """This handler tests 'Basic' authentication. It just sends a page with
1199 title 'user/pass' if you succeed."""
1200
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001201 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001202 return False
1203
1204 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001205 expected_password = 'secret'
1206 realm = 'testrealm'
1207 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001208
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001209 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1210 query_params = cgi.parse_qs(query, True)
1211 if 'set-cookie-if-challenged' in query_params:
1212 set_cookie_if_challenged = True
1213 if 'password' in query_params:
1214 expected_password = query_params['password'][0]
1215 if 'realm' in query_params:
1216 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001217
initial.commit94958cf2008-07-26 22:42:52 +00001218 auth = self.headers.getheader('authorization')
1219 try:
1220 if not auth:
1221 raise Exception('no auth')
1222 b64str = re.findall(r'Basic (\S+)', auth)[0]
1223 userpass = base64.b64decode(b64str)
1224 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001225 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001226 raise Exception('wrong password')
1227 except Exception, e:
1228 # Authentication failed.
1229 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001230 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001231 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001232 if set_cookie_if_challenged:
1233 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001234 self.end_headers()
1235 self.wfile.write('<html><head>')
1236 self.wfile.write('<title>Denied: %s</title>' % e)
1237 self.wfile.write('</head><body>')
1238 self.wfile.write('auth=%s<p>' % auth)
1239 self.wfile.write('b64str=%s<p>' % b64str)
1240 self.wfile.write('username: %s<p>' % username)
1241 self.wfile.write('userpass: %s<p>' % userpass)
1242 self.wfile.write('password: %s<p>' % password)
1243 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1244 self.wfile.write('</body></html>')
1245 return True
1246
1247 # Authentication successful. (Return a cachable response to allow for
1248 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001249 old_protocol_version = self.protocol_version
1250 self.protocol_version = "HTTP/1.1"
1251
initial.commit94958cf2008-07-26 22:42:52 +00001252 if_none_match = self.headers.getheader('if-none-match')
1253 if if_none_match == "abc":
1254 self.send_response(304)
1255 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001256 elif url_path.endswith(".gif"):
1257 # Using chrome/test/data/google/logo.gif as the test image
1258 test_image_path = ['google', 'logo.gif']
1259 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1260 if not os.path.isfile(gif_path):
1261 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001262 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001263 return True
1264
1265 f = open(gif_path, "rb")
1266 data = f.read()
1267 f.close()
1268
1269 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001270 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001271 self.send_header('Cache-control', 'max-age=60000')
1272 self.send_header('Etag', 'abc')
1273 self.end_headers()
1274 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001275 else:
1276 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001277 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001278 self.send_header('Cache-control', 'max-age=60000')
1279 self.send_header('Etag', 'abc')
1280 self.end_headers()
1281 self.wfile.write('<html><head>')
1282 self.wfile.write('<title>%s/%s</title>' % (username, password))
1283 self.wfile.write('</head><body>')
1284 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001285 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001286 self.wfile.write('</body></html>')
1287
rvargas@google.com54453b72011-05-19 01:11:11 +00001288 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001289 return True
1290
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001291 def GDataAuthHandler(self):
1292 """This handler verifies the Authentication header for GData requests."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001293
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001294 if not self.server.gdata_auth_token:
1295 # --auth-token is not specified, not the test case for GData.
1296 return False
1297
1298 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1299 return False
1300
1301 if 'GData-Version' not in self.headers:
1302 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1303 return True
1304
1305 if 'Authorization' not in self.headers:
1306 self.send_error(httplib.UNAUTHORIZED)
1307 return True
1308
1309 field_prefix = 'Bearer '
1310 authorization = self.headers['Authorization']
1311 if not authorization.startswith(field_prefix):
1312 self.send_error(httplib.UNAUTHORIZED)
1313 return True
1314
1315 code = authorization[len(field_prefix):]
1316 if code != self.server.gdata_auth_token:
1317 self.send_error(httplib.UNAUTHORIZED)
1318 return True
1319
1320 return False
1321
1322 def GDataDocumentsFeedQueryHandler(self):
1323 """This handler verifies if required parameters are properly
1324 specified for the GData DocumentsFeed request."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001325
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001326 if not self.server.gdata_auth_token:
1327 # --auth-token is not specified, not the test case for GData.
1328 return False
1329
1330 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1331 return False
1332
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001333 (_path, _question, query_params) = self.path.partition('?')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001334 self.query_params = urlparse.parse_qs(query_params)
1335
1336 if 'v' not in self.query_params:
1337 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1338 return True
1339 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1340 # currently our GData client only uses JSON format.
1341 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1342 return True
1343
1344 return False
1345
tonyg@chromium.org75054202010-03-31 22:06:10 +00001346 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001347 """Returns a nonce that's stable per request path for the server's lifetime.
1348 This is a fake implementation. A real implementation would only use a given
1349 nonce a single time (hence the name n-once). However, for the purposes of
1350 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001351
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001352 Args:
1353 force_reset: Iff set, the nonce will be changed. Useful for testing the
1354 "stale" response.
1355 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001356
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001357 if force_reset or not self.server.nonce_time:
1358 self.server.nonce_time = time.time()
1359 return hashlib.md5('privatekey%s%d' %
1360 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001361
1362 def AuthDigestHandler(self):
1363 """This handler tests 'Digest' authentication.
1364
1365 It just sends a page with title 'user/pass' if you succeed.
1366
1367 A stale response is sent iff "stale" is present in the request path.
1368 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001369
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001370 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001371 return False
1372
tonyg@chromium.org75054202010-03-31 22:06:10 +00001373 stale = 'stale' in self.path
1374 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001375 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001376 password = 'secret'
1377 realm = 'testrealm'
1378
1379 auth = self.headers.getheader('authorization')
1380 pairs = {}
1381 try:
1382 if not auth:
1383 raise Exception('no auth')
1384 if not auth.startswith('Digest'):
1385 raise Exception('not digest')
1386 # Pull out all the name="value" pairs as a dictionary.
1387 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1388
1389 # Make sure it's all valid.
1390 if pairs['nonce'] != nonce:
1391 raise Exception('wrong nonce')
1392 if pairs['opaque'] != opaque:
1393 raise Exception('wrong opaque')
1394
1395 # Check the 'response' value and make sure it matches our magic hash.
1396 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001397 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001398 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001399 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001400 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001401 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001402 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1403 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001404 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001405
1406 if pairs['response'] != response:
1407 raise Exception('wrong password')
1408 except Exception, e:
1409 # Authentication failed.
1410 self.send_response(401)
1411 hdr = ('Digest '
1412 'realm="%s", '
1413 'domain="/", '
1414 'qop="auth", '
1415 'algorithm=MD5, '
1416 'nonce="%s", '
1417 'opaque="%s"') % (realm, nonce, opaque)
1418 if stale:
1419 hdr += ', stale="TRUE"'
1420 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001421 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001422 self.end_headers()
1423 self.wfile.write('<html><head>')
1424 self.wfile.write('<title>Denied: %s</title>' % e)
1425 self.wfile.write('</head><body>')
1426 self.wfile.write('auth=%s<p>' % auth)
1427 self.wfile.write('pairs=%s<p>' % pairs)
1428 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1429 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1430 self.wfile.write('</body></html>')
1431 return True
1432
1433 # Authentication successful.
1434 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001435 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001436 self.end_headers()
1437 self.wfile.write('<html><head>')
1438 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1439 self.wfile.write('</head><body>')
1440 self.wfile.write('auth=%s<p>' % auth)
1441 self.wfile.write('pairs=%s<p>' % pairs)
1442 self.wfile.write('</body></html>')
1443
1444 return True
1445
1446 def SlowServerHandler(self):
1447 """Wait for the user suggested time before responding. The syntax is
1448 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001449
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001450 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001451 return False
1452 query_char = self.path.find('?')
1453 wait_sec = 1.0
1454 if query_char >= 0:
1455 try:
1456 wait_sec = int(self.path[query_char + 1:])
1457 except ValueError:
1458 pass
1459 time.sleep(wait_sec)
1460 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001461 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001462 self.end_headers()
1463 self.wfile.write("waited %d seconds" % wait_sec)
1464 return True
1465
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001466 def ChunkedServerHandler(self):
1467 """Send chunked response. Allows to specify chunks parameters:
1468 - waitBeforeHeaders - ms to wait before sending headers
1469 - waitBetweenChunks - ms to wait between chunks
1470 - chunkSize - size of each chunk in bytes
1471 - chunksNumber - number of chunks
1472 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1473 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001474
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001475 if not self._ShouldHandleRequest("/chunked"):
1476 return False
1477 query_char = self.path.find('?')
1478 chunkedSettings = {'waitBeforeHeaders' : 0,
1479 'waitBetweenChunks' : 0,
1480 'chunkSize' : 5,
1481 'chunksNumber' : 5}
1482 if query_char >= 0:
1483 params = self.path[query_char + 1:].split('&')
1484 for param in params:
1485 keyValue = param.split('=')
1486 if len(keyValue) == 2:
1487 try:
1488 chunkedSettings[keyValue[0]] = int(keyValue[1])
1489 except ValueError:
1490 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001491 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001492 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1493 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001494 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001495 self.send_header('Connection', 'close')
1496 self.send_header('Transfer-Encoding', 'chunked')
1497 self.end_headers()
1498 # Chunked encoding: sending all chunks, then final zero-length chunk and
1499 # then final CRLF.
1500 for i in range(0, chunkedSettings['chunksNumber']):
1501 if i > 0:
1502 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1503 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001504 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001505 self.sendChunkHelp('')
1506 return True
1507
initial.commit94958cf2008-07-26 22:42:52 +00001508 def ContentTypeHandler(self):
1509 """Returns a string of html with the given content type. E.g.,
1510 /contenttype?text/css returns an html file with the Content-Type
1511 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001512
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001513 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001514 return False
1515 query_char = self.path.find('?')
1516 content_type = self.path[query_char + 1:].strip()
1517 if not content_type:
1518 content_type = 'text/html'
1519 self.send_response(200)
1520 self.send_header('Content-Type', content_type)
1521 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001522 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001523 return True
1524
creis@google.com2f4f6a42011-03-25 19:44:19 +00001525 def NoContentHandler(self):
1526 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001527
creis@google.com2f4f6a42011-03-25 19:44:19 +00001528 if not self._ShouldHandleRequest("/nocontent"):
1529 return False
1530 self.send_response(204)
1531 self.end_headers()
1532 return True
1533
initial.commit94958cf2008-07-26 22:42:52 +00001534 def ServerRedirectHandler(self):
1535 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001536 '/server-redirect?http://foo.bar/asdf' to redirect to
1537 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001538
1539 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001540 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001541 return False
1542
1543 query_char = self.path.find('?')
1544 if query_char < 0 or len(self.path) <= query_char + 1:
1545 self.sendRedirectHelp(test_name)
1546 return True
1547 dest = self.path[query_char + 1:]
1548
1549 self.send_response(301) # moved permanently
1550 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001551 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001552 self.end_headers()
1553 self.wfile.write('<html><head>')
1554 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1555
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001556 return True
initial.commit94958cf2008-07-26 22:42:52 +00001557
1558 def ClientRedirectHandler(self):
1559 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001560 '/client-redirect?http://foo.bar/asdf' to redirect to
1561 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001562
1563 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001564 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001565 return False
1566
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001567 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001568 if query_char < 0 or len(self.path) <= query_char + 1:
1569 self.sendRedirectHelp(test_name)
1570 return True
1571 dest = self.path[query_char + 1:]
1572
1573 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001574 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001575 self.end_headers()
1576 self.wfile.write('<html><head>')
1577 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1578 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1579
1580 return True
1581
tony@chromium.org03266982010-03-05 03:18:42 +00001582 def MultipartHandler(self):
1583 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001584
tony@chromium.org4cb88302011-09-27 22:13:49 +00001585 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001586 if not self._ShouldHandleRequest(test_name):
1587 return False
1588
1589 num_frames = 10
1590 bound = '12345'
1591 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001592 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001593 'multipart/x-mixed-replace;boundary=' + bound)
1594 self.end_headers()
1595
1596 for i in xrange(num_frames):
1597 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001598 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001599 self.wfile.write('<title>page ' + str(i) + '</title>')
1600 self.wfile.write('page ' + str(i))
1601
1602 self.wfile.write('--' + bound + '--')
1603 return True
1604
tony@chromium.org4cb88302011-09-27 22:13:49 +00001605 def MultipartSlowHandler(self):
1606 """Send a multipart response (3 text/html pages) with a slight delay
1607 between each page. This is similar to how some pages show status using
1608 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001609
tony@chromium.org4cb88302011-09-27 22:13:49 +00001610 test_name = '/multipart-slow'
1611 if not self._ShouldHandleRequest(test_name):
1612 return False
1613
1614 num_frames = 3
1615 bound = '12345'
1616 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001617 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001618 'multipart/x-mixed-replace;boundary=' + bound)
1619 self.end_headers()
1620
1621 for i in xrange(num_frames):
1622 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001623 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001624 time.sleep(0.25)
1625 if i == 2:
1626 self.wfile.write('<title>PASS</title>')
1627 else:
1628 self.wfile.write('<title>page ' + str(i) + '</title>')
1629 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1630
1631 self.wfile.write('--' + bound + '--')
1632 return True
1633
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001634 def GetSSLSessionCacheHandler(self):
1635 """Send a reply containing a log of the session cache operations."""
1636
1637 if not self._ShouldHandleRequest('/ssl-session-cache'):
1638 return False
1639
1640 self.send_response(200)
1641 self.send_header('Content-Type', 'text/plain')
1642 self.end_headers()
1643 try:
1644 for (action, sessionID) in self.server.session_cache.log:
1645 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001646 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001647 self.wfile.write('Pass --https-record-resume in order to use' +
1648 ' this request')
1649 return True
1650
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001651 def CloseSocketHandler(self):
1652 """Closes the socket without sending anything."""
1653
1654 if not self._ShouldHandleRequest('/close-socket'):
1655 return False
1656
1657 self.wfile.close()
1658 return True
1659
initial.commit94958cf2008-07-26 22:42:52 +00001660 def DefaultResponseHandler(self):
1661 """This is the catch-all response handler for requests that aren't handled
1662 by one of the special handlers above.
1663 Note that we specify the content-length as without it the https connection
1664 is not closed properly (and the browser keeps expecting data)."""
1665
1666 contents = "Default response given for path: " + self.path
1667 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001668 self.send_header('Content-Type', 'text/html')
1669 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001670 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001671 if (self.command != 'HEAD'):
1672 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001673 return True
1674
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001675 def RedirectConnectHandler(self):
1676 """Sends a redirect to the CONNECT request for www.redirect.com. This
1677 response is not specified by the RFC, so the browser should not follow
1678 the redirect."""
1679
1680 if (self.path.find("www.redirect.com") < 0):
1681 return False
1682
1683 dest = "http://www.destination.com/foo.js"
1684
1685 self.send_response(302) # moved temporarily
1686 self.send_header('Location', dest)
1687 self.send_header('Connection', 'close')
1688 self.end_headers()
1689 return True
1690
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001691 def ServerAuthConnectHandler(self):
1692 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1693 response doesn't make sense because the proxy server cannot request
1694 server authentication."""
1695
1696 if (self.path.find("www.server-auth.com") < 0):
1697 return False
1698
1699 challenge = 'Basic realm="WallyWorld"'
1700
1701 self.send_response(401) # unauthorized
1702 self.send_header('WWW-Authenticate', challenge)
1703 self.send_header('Connection', 'close')
1704 self.end_headers()
1705 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001706
1707 def DefaultConnectResponseHandler(self):
1708 """This is the catch-all response handler for CONNECT requests that aren't
1709 handled by one of the special handlers above. Real Web servers respond
1710 with 400 to CONNECT requests."""
1711
1712 contents = "Your client has issued a malformed or illegal request."
1713 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001714 self.send_header('Content-Type', 'text/html')
1715 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001716 self.end_headers()
1717 self.wfile.write(contents)
1718 return True
1719
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001720 def DeviceManagementHandler(self):
1721 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001722
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001723 if not self._ShouldHandleRequest("/device_management"):
1724 return False
1725
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001726 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001727
1728 if not self.server._device_management_handler:
1729 import device_management
1730 policy_path = os.path.join(self.server.data_dir, 'device_management')
1731 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001732 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001733 self.server.policy_keys,
1734 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001735
1736 http_response, raw_reply = (
1737 self.server._device_management_handler.HandleRequest(self.path,
1738 self.headers,
1739 raw_request))
1740 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001741 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001742 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001743 self.end_headers()
1744 self.wfile.write(raw_reply)
1745 return True
1746
initial.commit94958cf2008-07-26 22:42:52 +00001747 # called by the redirect handling function when there is no parameter
1748 def sendRedirectHelp(self, redirect_name):
1749 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001750 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001751 self.end_headers()
1752 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1753 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1754 self.wfile.write('</body></html>')
1755
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001756 # called by chunked handling function
1757 def sendChunkHelp(self, chunk):
1758 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1759 self.wfile.write('%X\r\n' % len(chunk))
1760 self.wfile.write(chunk)
1761 self.wfile.write('\r\n')
1762
akalin@chromium.org154bb132010-11-12 02:20:27 +00001763
1764class SyncPageHandler(BasePageHandler):
1765 """Handler for the main HTTP sync server."""
1766
1767 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001768 get_handlers = [self.ChromiumSyncTimeHandler,
1769 self.ChromiumSyncMigrationOpHandler,
1770 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001771 self.ChromiumSyncDisableNotificationsOpHandler,
1772 self.ChromiumSyncEnableNotificationsOpHandler,
1773 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001774 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001775 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001776 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001777 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001778 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001779
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001780 post_handlers = [self.ChromiumSyncCommandHandler,
1781 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001782 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001783 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001784 post_handlers, [])
1785
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001786
akalin@chromium.org154bb132010-11-12 02:20:27 +00001787 def ChromiumSyncTimeHandler(self):
1788 """Handle Chromium sync .../time requests.
1789
1790 The syncer sometimes checks server reachability by examining /time.
1791 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001792
akalin@chromium.org154bb132010-11-12 02:20:27 +00001793 test_name = "/chromiumsync/time"
1794 if not self._ShouldHandleRequest(test_name):
1795 return False
1796
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001797 # Chrome hates it if we send a response before reading the request.
1798 if self.headers.getheader('content-length'):
1799 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001800 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001801
akalin@chromium.org154bb132010-11-12 02:20:27 +00001802 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001803 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001804 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001805 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001806 return True
1807
1808 def ChromiumSyncCommandHandler(self):
1809 """Handle a chromiumsync command arriving via http.
1810
1811 This covers all sync protocol commands: authentication, getupdates, and
1812 commit.
1813 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001814
akalin@chromium.org154bb132010-11-12 02:20:27 +00001815 test_name = "/chromiumsync/command"
1816 if not self._ShouldHandleRequest(test_name):
1817 return False
1818
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001819 length = int(self.headers.getheader('content-length'))
1820 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001821 http_response = 200
1822 raw_reply = None
1823 if not self.server.GetAuthenticated():
1824 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001825 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1826 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001827 else:
1828 http_response, raw_reply = self.server.HandleCommand(
1829 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001830
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001831 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001832 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001833 if http_response == 401:
1834 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001835 self.end_headers()
1836 self.wfile.write(raw_reply)
1837 return True
1838
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001839 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001840 test_name = "/chromiumsync/migrate"
1841 if not self._ShouldHandleRequest(test_name):
1842 return False
1843
1844 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1845 self.path)
1846 self.send_response(http_response)
1847 self.send_header('Content-Type', 'text/html')
1848 self.send_header('Content-Length', len(raw_reply))
1849 self.end_headers()
1850 self.wfile.write(raw_reply)
1851 return True
1852
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001853 def ChromiumSyncCredHandler(self):
1854 test_name = "/chromiumsync/cred"
1855 if not self._ShouldHandleRequest(test_name):
1856 return False
1857 try:
1858 query = urlparse.urlparse(self.path)[4]
1859 cred_valid = urlparse.parse_qs(query)['valid']
1860 if cred_valid[0] == 'True':
1861 self.server.SetAuthenticated(True)
1862 else:
1863 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001864 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001865 self.server.SetAuthenticated(False)
1866
1867 http_response = 200
1868 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1869 self.send_response(http_response)
1870 self.send_header('Content-Type', 'text/html')
1871 self.send_header('Content-Length', len(raw_reply))
1872 self.end_headers()
1873 self.wfile.write(raw_reply)
1874 return True
1875
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001876 def ChromiumSyncDisableNotificationsOpHandler(self):
1877 test_name = "/chromiumsync/disablenotifications"
1878 if not self._ShouldHandleRequest(test_name):
1879 return False
1880 self.server.GetXmppServer().DisableNotifications()
1881 result = 200
1882 raw_reply = ('<html><title>Notifications disabled</title>'
1883 '<H1>Notifications disabled</H1></html>')
1884 self.send_response(result)
1885 self.send_header('Content-Type', 'text/html')
1886 self.send_header('Content-Length', len(raw_reply))
1887 self.end_headers()
1888 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001889 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001890
1891 def ChromiumSyncEnableNotificationsOpHandler(self):
1892 test_name = "/chromiumsync/enablenotifications"
1893 if not self._ShouldHandleRequest(test_name):
1894 return False
1895 self.server.GetXmppServer().EnableNotifications()
1896 result = 200
1897 raw_reply = ('<html><title>Notifications enabled</title>'
1898 '<H1>Notifications enabled</H1></html>')
1899 self.send_response(result)
1900 self.send_header('Content-Type', 'text/html')
1901 self.send_header('Content-Length', len(raw_reply))
1902 self.end_headers()
1903 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001904 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001905
1906 def ChromiumSyncSendNotificationOpHandler(self):
1907 test_name = "/chromiumsync/sendnotification"
1908 if not self._ShouldHandleRequest(test_name):
1909 return False
1910 query = urlparse.urlparse(self.path)[4]
1911 query_params = urlparse.parse_qs(query)
1912 channel = ''
1913 data = ''
1914 if 'channel' in query_params:
1915 channel = query_params['channel'][0]
1916 if 'data' in query_params:
1917 data = query_params['data'][0]
1918 self.server.GetXmppServer().SendNotification(channel, data)
1919 result = 200
1920 raw_reply = ('<html><title>Notification sent</title>'
1921 '<H1>Notification sent with channel "%s" '
1922 'and data "%s"</H1></html>'
1923 % (channel, data))
1924 self.send_response(result)
1925 self.send_header('Content-Type', 'text/html')
1926 self.send_header('Content-Length', len(raw_reply))
1927 self.end_headers()
1928 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001929 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001930
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001931 def ChromiumSyncBirthdayErrorOpHandler(self):
1932 test_name = "/chromiumsync/birthdayerror"
1933 if not self._ShouldHandleRequest(test_name):
1934 return False
1935 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1936 self.send_response(result)
1937 self.send_header('Content-Type', 'text/html')
1938 self.send_header('Content-Length', len(raw_reply))
1939 self.end_headers()
1940 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001941 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001942
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001943 def ChromiumSyncTransientErrorOpHandler(self):
1944 test_name = "/chromiumsync/transienterror"
1945 if not self._ShouldHandleRequest(test_name):
1946 return False
1947 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1948 self.send_response(result)
1949 self.send_header('Content-Type', 'text/html')
1950 self.send_header('Content-Length', len(raw_reply))
1951 self.end_headers()
1952 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001953 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001954
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001955 def ChromiumSyncErrorOpHandler(self):
1956 test_name = "/chromiumsync/error"
1957 if not self._ShouldHandleRequest(test_name):
1958 return False
1959 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1960 self.path)
1961 self.send_response(result)
1962 self.send_header('Content-Type', 'text/html')
1963 self.send_header('Content-Length', len(raw_reply))
1964 self.end_headers()
1965 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001966 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001967
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001968 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1969 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001970 if not self._ShouldHandleRequest(test_name):
1971 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001972 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001973 self.send_response(result)
1974 self.send_header('Content-Type', 'text/html')
1975 self.send_header('Content-Length', len(raw_reply))
1976 self.end_headers()
1977 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001978 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001979
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001980 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1981 test_name = "/chromiumsync/createsyncedbookmarks"
1982 if not self._ShouldHandleRequest(test_name):
1983 return False
1984 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1985 self.send_response(result)
1986 self.send_header('Content-Type', 'text/html')
1987 self.send_header('Content-Length', len(raw_reply))
1988 self.end_headers()
1989 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001990 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001991
akalin@chromium.org154bb132010-11-12 02:20:27 +00001992
newt@chromium.org1fc32742012-10-20 00:28:35 +00001993def MakeDataDir(options):
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001994 if options.data_dir:
1995 if not os.path.isdir(options.data_dir):
1996 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1997 return None
1998 my_data_dir = options.data_dir
1999 else:
2000 # Create the default path to our data dir, relative to the exe dir.
2001 my_data_dir = os.path.dirname(sys.argv[0])
2002 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
2003 "test", "data")
2004
2005 #TODO(ibrar): Must use Find* funtion defined in google\tools
2006 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
2007
2008 return my_data_dir
2009
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002010class OCSPHandler(BasePageHandler):
2011 def __init__(self, request, client_address, socket_server):
2012 handlers = [self.OCSPResponse]
2013 self.ocsp_response = socket_server.ocsp_response
2014 BasePageHandler.__init__(self, request, client_address, socket_server,
2015 [], handlers, [], handlers, [])
2016
2017 def OCSPResponse(self):
2018 self.send_response(200)
2019 self.send_header('Content-Type', 'application/ocsp-response')
2020 self.send_header('Content-Length', str(len(self.ocsp_response)))
2021 self.end_headers()
2022
2023 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002024
2025class TCPEchoHandler(SocketServer.BaseRequestHandler):
2026 """The RequestHandler class for TCP echo server.
2027
2028 It is instantiated once per connection to the server, and overrides the
2029 handle() method to implement communication to the client.
2030 """
2031
2032 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002033 """Handles the request from the client and constructs a response."""
2034
2035 data = self.request.recv(65536).strip()
2036 # Verify the "echo request" message received from the client. Send back
2037 # "echo response" message if "echo request" message is valid.
2038 try:
2039 return_data = echo_message.GetEchoResponseData(data)
2040 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002041 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002042 except ValueError:
2043 return
2044
2045 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002046
2047
2048class UDPEchoHandler(SocketServer.BaseRequestHandler):
2049 """The RequestHandler class for UDP echo server.
2050
2051 It is instantiated once per connection to the server, and overrides the
2052 handle() method to implement communication to the client.
2053 """
2054
2055 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002056 """Handles the request from the client and constructs a response."""
2057
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002058 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002059 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002060 # Verify the "echo request" message received from the client. Send back
2061 # "echo response" message if "echo request" message is valid.
2062 try:
2063 return_data = echo_message.GetEchoResponseData(data)
2064 if not return_data:
2065 return
2066 except ValueError:
2067 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002068 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002069
2070
bashi@chromium.org33233532012-09-08 17:37:24 +00002071class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2072 """A request handler that behaves as a proxy server which requires
2073 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2074 """
2075
2076 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2077
2078 def parse_request(self):
2079 """Overrides parse_request to check credential."""
2080
2081 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2082 return False
2083
2084 auth = self.headers.getheader('Proxy-Authorization')
2085 if auth != self._AUTH_CREDENTIAL:
2086 self.send_response(407)
2087 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2088 self.end_headers()
2089 return False
2090
2091 return True
2092
2093 def _start_read_write(self, sock):
2094 sock.setblocking(0)
2095 self.request.setblocking(0)
2096 rlist = [self.request, sock]
2097 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002098 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002099 if errors:
2100 self.send_response(500)
2101 self.end_headers()
2102 return
2103 for s in ready_sockets:
2104 received = s.recv(1024)
2105 if len(received) == 0:
2106 return
2107 if s == self.request:
2108 other = sock
2109 else:
2110 other = self.request
2111 other.send(received)
2112
2113 def _do_common_method(self):
2114 url = urlparse.urlparse(self.path)
2115 port = url.port
2116 if not port:
2117 if url.scheme == 'http':
2118 port = 80
2119 elif url.scheme == 'https':
2120 port = 443
2121 if not url.hostname or not port:
2122 self.send_response(400)
2123 self.end_headers()
2124 return
2125
2126 if len(url.path) == 0:
2127 path = '/'
2128 else:
2129 path = url.path
2130 if len(url.query) > 0:
2131 path = '%s?%s' % (url.path, url.query)
2132
2133 sock = None
2134 try:
2135 sock = socket.create_connection((url.hostname, port))
2136 sock.send('%s %s %s\r\n' % (
2137 self.command, path, self.protocol_version))
2138 for header in self.headers.headers:
2139 header = header.strip()
2140 if (header.lower().startswith('connection') or
2141 header.lower().startswith('proxy')):
2142 continue
2143 sock.send('%s\r\n' % header)
2144 sock.send('\r\n')
2145 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002146 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002147 self.send_response(500)
2148 self.end_headers()
2149 finally:
2150 if sock is not None:
2151 sock.close()
2152
2153 def do_CONNECT(self):
2154 try:
2155 pos = self.path.rfind(':')
2156 host = self.path[:pos]
2157 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002158 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002159 self.send_response(400)
2160 self.end_headers()
2161
2162 try:
2163 sock = socket.create_connection((host, port))
2164 self.send_response(200, 'Connection established')
2165 self.end_headers()
2166 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002167 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002168 self.send_response(500)
2169 self.end_headers()
2170 finally:
2171 sock.close()
2172
2173 def do_GET(self):
2174 self._do_common_method()
2175
2176 def do_HEAD(self):
2177 self._do_common_method()
2178
2179
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002180class FileMultiplexer:
2181 def __init__(self, fd1, fd2) :
2182 self.__fd1 = fd1
2183 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002184
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002185 def __del__(self) :
newt@chromium.org1fc32742012-10-20 00:28:35 +00002186 self.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002187
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002188 def write(self, text) :
2189 self.__fd1.write(text)
2190 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002191
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002192 def flush(self) :
2193 self.__fd1.flush()
2194 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002195
newt@chromium.org1fc32742012-10-20 00:28:35 +00002196 def close(self):
2197 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2198 self.__fd1.close()
2199 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2200 self.__fd2.close()
2201
2202
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002203def main(options, _args):
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002204 logfile = open('testserver.log', 'w')
2205 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2206 if options.log_to_console:
2207 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2208 else:
2209 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002210
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002211 port = options.port
2212 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002213
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002214 server_data = {}
2215 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002216
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002217 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002218
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002219 if options.server_type == SERVER_HTTP:
2220 if options.https:
2221 pem_cert_and_key = None
2222 if options.cert_and_key_file:
2223 if not os.path.isfile(options.cert_and_key_file):
2224 print ('specified server cert file not found: ' +
2225 options.cert_and_key_file + ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002226 return 1
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002227 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002228 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002229 # generate a new certificate and run an OCSP server for it.
2230 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2231 print ('OCSP server started on %s:%d...' %
2232 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002233
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002234 ocsp_der = None
2235 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002236
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002237 if options.ocsp == 'ok':
2238 ocsp_state = minica.OCSP_STATE_GOOD
2239 elif options.ocsp == 'revoked':
2240 ocsp_state = minica.OCSP_STATE_REVOKED
2241 elif options.ocsp == 'invalid':
2242 ocsp_state = minica.OCSP_STATE_INVALID
2243 elif options.ocsp == 'unauthorized':
2244 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2245 elif options.ocsp == 'unknown':
2246 ocsp_state = minica.OCSP_STATE_UNKNOWN
2247 else:
2248 print 'unknown OCSP status: ' + options.ocsp_status
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002249 return 1
mattm@chromium.org07e28412012-09-05 00:19:41 +00002250
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002251 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2252 subject = "127.0.0.1",
2253 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server.server_port)),
2254 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002255
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002256 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002257
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002258 for ca_cert in options.ssl_client_ca:
2259 if not os.path.isfile(ca_cert):
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002260 print ('specified trusted client CA file not found: ' + ca_cert +
2261 ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002262 return 1
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002263 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2264 options.ssl_client_auth, options.ssl_client_ca,
2265 options.ssl_bulk_cipher, options.record_resume,
2266 options.tls_intolerant)
2267 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002268 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002269 server = HTTPServer((host, port), TestPageHandler)
2270 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002271
newt@chromium.org1fc32742012-10-20 00:28:35 +00002272 server.data_dir = MakeDataDir(options)
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002273 server.file_root_url = options.file_root_url
2274 server_data['port'] = server.server_port
2275 server._device_management_handler = None
2276 server.policy_keys = options.policy_keys
2277 server.policy_user = options.policy_user
2278 server.gdata_auth_token = options.auth_token
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002279 elif options.server_type == SERVER_WEBSOCKET:
2280 # Launch pywebsocket via WebSocketServer.
2281 logger = logging.getLogger()
2282 logger.addHandler(logging.StreamHandler())
2283 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2284 # is required to work correctly. It should be fixed from pywebsocket side.
newt@chromium.org1fc32742012-10-20 00:28:35 +00002285 os.chdir(MakeDataDir(options))
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002286 websocket_options = WebSocketOptions(host, port, '.')
2287 if options.cert_and_key_file:
2288 websocket_options.use_tls = True
2289 websocket_options.private_key = options.cert_and_key_file
2290 websocket_options.certificate = options.cert_and_key_file
2291 if options.ssl_client_auth:
2292 websocket_options.tls_client_auth = True
2293 if len(options.ssl_client_ca) != 1:
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002294 print 'one trusted client CA file should be specified'
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002295 return 1
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002296 if not os.path.isfile(options.ssl_client_ca[0]):
2297 print ('specified trusted client CA file not found: ' +
2298 options.ssl_client_ca[0] + ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002299 return 1
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002300 websocket_options.tls_client_ca = options.ssl_client_ca[0]
2301 server = WebSocketServer(websocket_options)
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002302 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2303 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002304 elif options.server_type == SERVER_SYNC:
2305 xmpp_port = options.xmpp_port
2306 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2307 print 'Sync HTTP server started on port %d...' % server.server_port
2308 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2309 server_data['port'] = server.server_port
2310 server_data['xmpp_port'] = server.xmpp_port
2311 elif options.server_type == SERVER_TCP_ECHO:
2312 # Used for generating the key (randomly) that encodes the "echo request"
2313 # message.
2314 random.seed()
2315 server = TCPEchoServer((host, port), TCPEchoHandler)
2316 print 'Echo TCP server started on port %d...' % server.server_port
2317 server_data['port'] = server.server_port
2318 elif options.server_type == SERVER_UDP_ECHO:
2319 # Used for generating the key (randomly) that encodes the "echo request"
2320 # message.
2321 random.seed()
2322 server = UDPEchoServer((host, port), UDPEchoHandler)
2323 print 'Echo UDP server started on port %d...' % server.server_port
2324 server_data['port'] = server.server_port
bashi@chromium.org33233532012-09-08 17:37:24 +00002325 elif options.server_type == SERVER_BASIC_AUTH_PROXY:
2326 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2327 print 'BasicAuthProxy server started on port %d...' % server.server_port
2328 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002329 # means FTP Server
2330 else:
newt@chromium.org1fc32742012-10-20 00:28:35 +00002331 my_data_dir = MakeDataDir(options)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002332
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002333 # Instantiate a dummy authorizer for managing 'virtual' users
2334 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002335
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002336 # Define a new user having full r/w permissions and a read-only
2337 # anonymous user
2338 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002339
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002340 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002341
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002342 # Instantiate FTP handler class
2343 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2344 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002345
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002346 # Define a customized banner (string returned when client connects)
2347 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2348 pyftpdlib.ftpserver.__ver__)
2349
2350 # Instantiate FTP server class and listen to address:port
2351 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2352 server_data['port'] = server.socket.getsockname()[1]
2353 print 'FTP server started on port %d...' % server_data['port']
2354
2355 # Notify the parent that we've started. (BaseServer subclasses
2356 # bind their sockets on construction.)
2357 if options.startup_pipe is not None:
2358 server_data_json = json.dumps(server_data)
2359 server_data_len = len(server_data_json)
2360 print 'sending server_data: %s (%d bytes)' % (
2361 server_data_json, server_data_len)
2362 if sys.platform == 'win32':
2363 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2364 else:
2365 fd = options.startup_pipe
2366 startup_pipe = os.fdopen(fd, "w")
2367 # First write the data length as an unsigned 4-byte value. This
2368 # is _not_ using network byte ordering since the other end of the
2369 # pipe is on the same machine.
2370 startup_pipe.write(struct.pack('=L', server_data_len))
2371 startup_pipe.write(server_data_json)
2372 startup_pipe.close()
2373
2374 if ocsp_server is not None:
2375 ocsp_server.serve_forever_on_thread()
2376
2377 try:
2378 server.serve_forever()
2379 except KeyboardInterrupt:
2380 print 'shutting down server'
2381 if ocsp_server is not None:
2382 ocsp_server.stop_serving()
2383 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002384
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002385 return 0
2386
initial.commit94958cf2008-07-26 22:42:52 +00002387if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002388 option_parser = optparse.OptionParser()
2389 option_parser.add_option("-f", '--ftp', action='store_const',
2390 const=SERVER_FTP, default=SERVER_HTTP,
2391 dest='server_type',
2392 help='start up an FTP server.')
2393 option_parser.add_option('', '--sync', action='store_const',
2394 const=SERVER_SYNC, default=SERVER_HTTP,
2395 dest='server_type',
2396 help='start up a sync server.')
2397 option_parser.add_option('', '--tcp-echo', action='store_const',
2398 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2399 dest='server_type',
2400 help='start up a tcp echo server.')
2401 option_parser.add_option('', '--udp-echo', action='store_const',
2402 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2403 dest='server_type',
2404 help='start up a udp echo server.')
bashi@chromium.org33233532012-09-08 17:37:24 +00002405 option_parser.add_option('', '--basic-auth-proxy', action='store_const',
2406 const=SERVER_BASIC_AUTH_PROXY, default=SERVER_HTTP,
2407 dest='server_type',
2408 help='start up a proxy server which requires basic '
2409 'authentication.')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002410 option_parser.add_option('', '--websocket', action='store_const',
2411 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2412 dest='server_type',
2413 help='start up a WebSocket server.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002414 option_parser.add_option('', '--log-to-console', action='store_const',
2415 const=True, default=False,
2416 dest='log_to_console',
2417 help='Enables or disables sys.stdout logging to '
2418 'the console.')
2419 option_parser.add_option('', '--port', default='0', type='int',
2420 help='Port used by the server. If unspecified, the '
2421 'server will listen on an ephemeral port.')
2422 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2423 help='Port used by the XMPP server. If unspecified, '
2424 'the XMPP server will listen on an ephemeral port.')
2425 option_parser.add_option('', '--data-dir', dest='data_dir',
2426 help='Directory from which to read the files.')
2427 option_parser.add_option('', '--https', action='store_true', dest='https',
2428 help='Specify that https should be used.')
2429 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2430 help='specify the path to the file containing the '
2431 'certificate and private key for the server in PEM '
2432 'format')
2433 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2434 help='The type of OCSP response generated for the '
2435 'automatically generated certificate. One of '
2436 '[ok,revoked,invalid]')
2437 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2438 default='0', type='int',
2439 help='If nonzero, certain TLS connections will be'
2440 ' aborted in order to test version fallback. 1'
2441 ' means all TLS versions will be aborted. 2 means'
2442 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2443 ' 1.2 or higher will be aborted.')
2444 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2445 const=True, default=False, action='store_const',
2446 help='Record resumption cache events rather than'
2447 ' resuming as normal. Allows the use of the'
2448 ' /ssl-session-cache request')
2449 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2450 help='Require SSL client auth on every connection.')
2451 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2452 help='Specify that the client certificate request '
2453 'should include the CA named in the subject of '
2454 'the DER-encoded certificate contained in the '
2455 'specified file. This option may appear multiple '
2456 'times, indicating multiple CA names should be '
2457 'sent in the request.')
2458 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2459 help='Specify the bulk encryption algorithm(s)'
2460 'that will be accepted by the SSL server. Valid '
2461 'values are "aes256", "aes128", "3des", "rc4". If '
2462 'omitted, all algorithms will be used. This '
2463 'option may appear multiple times, indicating '
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002464 'multiple algorithms should be enabled.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002465 option_parser.add_option('', '--file-root-url', default='/files/',
2466 help='Specify a root URL for files served.')
2467 option_parser.add_option('', '--startup-pipe', type='int',
2468 dest='startup_pipe',
2469 help='File handle of pipe to parent process')
2470 option_parser.add_option('', '--policy-key', action='append',
2471 dest='policy_keys',
2472 help='Specify a path to a PEM-encoded private key '
2473 'to use for policy signing. May be specified '
2474 'multiple times in order to load multipe keys into '
2475 'the server. If ther server has multiple keys, it '
2476 'will rotate through them in at each request a '
2477 'round-robin fashion. The server will generate a '
2478 'random key if none is specified on the command '
2479 'line.')
2480 option_parser.add_option('', '--policy-user', default='user@example.com',
2481 dest='policy_user',
2482 help='Specify the user name the server should '
2483 'report back to the client as the user owning the '
2484 'token used for making the policy request.')
2485 option_parser.add_option('', '--host', default='127.0.0.1',
2486 dest='host',
2487 help='Hostname or IP upon which the server will '
2488 'listen. Client connections will also only be '
2489 'allowed from this address.')
2490 option_parser.add_option('', '--auth-token', dest='auth_token',
2491 help='Specify the auth token which should be used'
2492 'in the authorization header for GData.')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002493 main_options, main_args = option_parser.parse_args()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002494
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002495 sys.exit(main(main_options, main_args))
2496