blob: 31fd3c58af0ce8e47d610e33707f2fabe628ddf6 [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.orgc960f6d2012-11-06 07:58:37 +000043from mod_pywebsocket.standalone import WebSocketServer
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000044import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000045import tlslite
46import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000047
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000048if sys.platform == 'win32':
49 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000050
maruel@chromium.org756cf982009-03-05 12:46:38 +000051SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000052SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000053SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000054SERVER_TCP_ECHO = 3
55SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000056SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000057SERVER_WEBSOCKET = 6
58
59# Default request queue size for WebSocketServer.
60_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000061
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000062# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000063debug_output = sys.stderr
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +000064def debug(string):
65 debug_output.write(string + "\n")
initial.commit94958cf2008-07-26 22:42:52 +000066 debug_output.flush()
67
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000068class WebSocketOptions:
69 """Holds options for WebSocketServer."""
70
71 def __init__(self, host, port, data_dir):
72 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
73 self.server_host = host
74 self.port = port
75 self.websock_handlers = data_dir
76 self.scan_dir = None
77 self.allow_handlers_outside_root_dir = False
78 self.websock_handlers_map_file = None
79 self.cgi_directories = []
80 self.is_executable_method = None
81 self.allow_draft75 = False
82 self.strict = True
83
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000084 self.use_tls = False
85 self.private_key = None
86 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000087 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000088 self.tls_client_ca = None
89 self.use_basic_auth = False
90
agl@chromium.orgf9e66792011-12-12 22:22:19 +000091class RecordingSSLSessionCache(object):
92 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
93 lookups and inserts in order to test session cache behaviours."""
94
95 def __init__(self):
96 self.log = []
97
98 def __getitem__(self, sessionID):
99 self.log.append(('lookup', sessionID))
100 raise KeyError()
101
102 def __setitem__(self, sessionID, session):
103 self.log.append(('insert', sessionID))
104
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000105
106class ClientRestrictingServerMixIn:
107 """Implements verify_request to limit connections to our configured IP
108 address."""
109
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000110 def verify_request(self, _request, client_address):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000111 return client_address[0] == self.server_address[0]
112
113
initial.commit94958cf2008-07-26 22:42:52 +0000114class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000115 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000116 to be exited cleanly (by setting its "stop" member to True)."""
117
118 def serve_forever(self):
119 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000120 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000121 while not self.stop:
122 self.handle_request()
123 self.socket.close()
124
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000125
126class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000127 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000128 verification."""
129
130 pass
131
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
133 """This is a specialization of HTTPServer that serves an
134 OCSP response"""
135
136 def serve_forever_on_thread(self):
137 self.thread = threading.Thread(target = self.serve_forever,
138 name = "OCSPServerThread")
139 self.thread.start()
140
141 def stop_serving(self):
142 self.shutdown()
143 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000144
145class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
146 ClientRestrictingServerMixIn,
147 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000148 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000149 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000150
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000151 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000152 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000153 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000154 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
155 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000156 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000157 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000158 self.tls_intolerant = tls_intolerant
159
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000160 for ca_file in ssl_client_cas:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000161 s = open(ca_file).read()
162 x509 = tlslite.api.X509()
163 x509.parse(s)
164 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000165 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
166 if ssl_bulk_ciphers is not None:
167 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000168
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000169 if record_resume_info:
170 # If record_resume_info is true then we'll replace the session cache with
171 # an object that records the lookups and inserts that it sees.
172 self.session_cache = RecordingSSLSessionCache()
173 else:
174 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000175 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
176
177 def handshake(self, tlsConnection):
178 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000179
initial.commit94958cf2008-07-26 22:42:52 +0000180 try:
181 tlsConnection.handshakeServer(certChain=self.cert_chain,
182 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000183 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000184 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000185 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000186 reqCAs=self.ssl_client_cas,
187 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000188 tlsConnection.ignoreAbruptClose = True
189 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000190 except tlslite.api.TLSAbruptCloseError:
191 # Ignore abrupt close.
192 return True
initial.commit94958cf2008-07-26 22:42:52 +0000193 except tlslite.api.TLSError, error:
194 print "Handshake failure:", str(error)
195 return False
196
akalin@chromium.org154bb132010-11-12 02:20:27 +0000197
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000198class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000199 """An HTTP server that handles sync commands."""
200
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000201 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000202 # We import here to avoid pulling in chromiumsync's dependencies
203 # unless strictly necessary.
204 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000205 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000206 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000207 self._sync_handler = chromiumsync.TestServer()
208 self._xmpp_socket_map = {}
209 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000210 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000211 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000212 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000213
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000214 def GetXmppServer(self):
215 return self._xmpp_server
216
akalin@chromium.org154bb132010-11-12 02:20:27 +0000217 def HandleCommand(self, query, raw_request):
218 return self._sync_handler.HandleCommand(query, raw_request)
219
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000220 def HandleRequestNoBlock(self):
221 """Handles a single request.
222
223 Copied from SocketServer._handle_request_noblock().
224 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000225
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000226 try:
227 request, client_address = self.get_request()
228 except socket.error:
229 return
230 if self.verify_request(request, client_address):
231 try:
232 self.process_request(request, client_address)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000233 except Exception:
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000234 self.handle_error(request, client_address)
235 self.close_request(request)
236
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000237 def SetAuthenticated(self, auth_valid):
238 self.authenticated = auth_valid
239
240 def GetAuthenticated(self):
241 return self.authenticated
242
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000243 def serve_forever(self):
244 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
245 """
246
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000247 def HandleXmppSocket(fd, socket_map, handler):
248 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000249
250 Adapted from asyncore.read() et al.
251 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000252
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000253 xmpp_connection = socket_map.get(fd)
254 # This could happen if a previous handler call caused fd to get
255 # removed from socket_map.
256 if xmpp_connection is None:
257 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000258 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000259 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000260 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
261 raise
262 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000263 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000264
265 while True:
266 read_fds = [ self.fileno() ]
267 write_fds = []
268 exceptional_fds = []
269
270 for fd, xmpp_connection in self._xmpp_socket_map.items():
271 is_r = xmpp_connection.readable()
272 is_w = xmpp_connection.writable()
273 if is_r:
274 read_fds.append(fd)
275 if is_w:
276 write_fds.append(fd)
277 if is_r or is_w:
278 exceptional_fds.append(fd)
279
280 try:
281 read_fds, write_fds, exceptional_fds = (
282 select.select(read_fds, write_fds, exceptional_fds))
283 except select.error, err:
284 if err.args[0] != errno.EINTR:
285 raise
286 else:
287 continue
288
289 for fd in read_fds:
290 if fd == self.fileno():
291 self.HandleRequestNoBlock()
292 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000293 HandleXmppSocket(fd, self._xmpp_socket_map,
294 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000295
296 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000297 HandleXmppSocket(fd, self._xmpp_socket_map,
298 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000299
300 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000301 HandleXmppSocket(fd, self._xmpp_socket_map,
302 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000303
akalin@chromium.org154bb132010-11-12 02:20:27 +0000304
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000305class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
306 """This is a specialization of FTPServer that adds client verification."""
307
308 pass
309
310
311class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000312 """A TCP echo server that echoes back what it has received."""
313
314 def server_bind(self):
315 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000316
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000317 SocketServer.TCPServer.server_bind(self)
318 host, port = self.socket.getsockname()[:2]
319 self.server_name = socket.getfqdn(host)
320 self.server_port = port
321
322 def serve_forever(self):
323 self.stop = False
324 self.nonce_time = None
325 while not self.stop:
326 self.handle_request()
327 self.socket.close()
328
329
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000330class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000331 """A UDP echo server that echoes back what it has received."""
332
333 def server_bind(self):
334 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000335
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000336 SocketServer.UDPServer.server_bind(self)
337 host, port = self.socket.getsockname()[:2]
338 self.server_name = socket.getfqdn(host)
339 self.server_port = port
340
341 def serve_forever(self):
342 self.stop = False
343 self.nonce_time = None
344 while not self.stop:
345 self.handle_request()
346 self.socket.close()
347
348
akalin@chromium.org154bb132010-11-12 02:20:27 +0000349class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
350
351 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000352 connect_handlers, get_handlers, head_handlers, post_handlers,
353 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000354 self._connect_handlers = connect_handlers
355 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000356 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000357 self._post_handlers = post_handlers
358 self._put_handlers = put_handlers
359 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
360 self, request, client_address, socket_server)
361
362 def log_request(self, *args, **kwargs):
363 # Disable request logging to declutter test log output.
364 pass
365
366 def _ShouldHandleRequest(self, handler_name):
367 """Determines if the path can be handled by the handler.
368
369 We consider a handler valid if the path begins with the
370 handler name. It can optionally be followed by "?*", "/*".
371 """
372
373 pattern = re.compile('%s($|\?|/).*' % handler_name)
374 return pattern.match(self.path)
375
376 def do_CONNECT(self):
377 for handler in self._connect_handlers:
378 if handler():
379 return
380
381 def do_GET(self):
382 for handler in self._get_handlers:
383 if handler():
384 return
385
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000386 def do_HEAD(self):
387 for handler in self._head_handlers:
388 if handler():
389 return
390
akalin@chromium.org154bb132010-11-12 02:20:27 +0000391 def do_POST(self):
392 for handler in self._post_handlers:
393 if handler():
394 return
395
396 def do_PUT(self):
397 for handler in self._put_handlers:
398 if handler():
399 return
400
401
402class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000403
404 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000405 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000406 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000407 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000408 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000409 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000410 self.NoCacheMaxAgeTimeHandler,
411 self.NoCacheTimeHandler,
412 self.CacheTimeHandler,
413 self.CacheExpiresHandler,
414 self.CacheProxyRevalidateHandler,
415 self.CachePrivateHandler,
416 self.CachePublicHandler,
417 self.CacheSMaxAgeHandler,
418 self.CacheMustRevalidateHandler,
419 self.CacheMustRevalidateMaxAgeHandler,
420 self.CacheNoStoreHandler,
421 self.CacheNoStoreMaxAgeHandler,
422 self.CacheNoTransformHandler,
423 self.DownloadHandler,
424 self.DownloadFinishHandler,
425 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000426 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000427 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000428 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000429 self.GDataAuthHandler,
430 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000431 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000432 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000433 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000434 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000435 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000436 self.AuthBasicHandler,
437 self.AuthDigestHandler,
438 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000439 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000441 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000442 self.ServerRedirectHandler,
443 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000444 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000445 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000446 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000447 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000449 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000450 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000451 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000452 self.DeviceManagementHandler,
453 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000454 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000455 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000456 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000457 head_handlers = [
458 self.FileHandler,
459 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000460
maruel@google.come250a9b2009-03-10 17:39:46 +0000461 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000462 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000463 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000464 'gif': 'image/gif',
465 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000466 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000467 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000468 'pdf' : 'application/pdf',
469 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000470 }
initial.commit94958cf2008-07-26 22:42:52 +0000471 self._default_mime_type = 'text/html'
472
akalin@chromium.org154bb132010-11-12 02:20:27 +0000473 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000474 connect_handlers, get_handlers, head_handlers,
475 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476
initial.commit94958cf2008-07-26 22:42:52 +0000477 def GetMIMETypeFromName(self, file_name):
478 """Returns the mime type for the specified file_name. So far it only looks
479 at the file extension."""
480
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000481 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000482 if len(extension) == 0:
483 # no extension.
484 return self._default_mime_type
485
ericroman@google.comc17ca532009-05-07 03:51:05 +0000486 # extension starts with a dot, so we need to remove it
487 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000488
initial.commit94958cf2008-07-26 22:42:52 +0000489 def NoCacheMaxAgeTimeHandler(self):
490 """This request handler yields a page with the title set to the current
491 system time, and no caching requested."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
497 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000498 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000499 self.end_headers()
500
maruel@google.come250a9b2009-03-10 17:39:46 +0000501 self.wfile.write('<html><head><title>%s</title></head></html>' %
502 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000503
504 return True
505
506 def NoCacheTimeHandler(self):
507 """This request handler yields a page with the title set to the current
508 system time, and no caching requested."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
514 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000516 self.end_headers()
517
maruel@google.come250a9b2009-03-10 17:39:46 +0000518 self.wfile.write('<html><head><title>%s</title></head></html>' %
519 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000520
521 return True
522
523 def CacheTimeHandler(self):
524 """This request handler yields a page with the title set to the current
525 system time, and allows caching for one minute."""
526
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000527 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000528 return False
529
530 self.send_response(200)
531 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000532 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000533 self.end_headers()
534
maruel@google.come250a9b2009-03-10 17:39:46 +0000535 self.wfile.write('<html><head><title>%s</title></head></html>' %
536 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000537
538 return True
539
540 def CacheExpiresHandler(self):
541 """This request handler yields a page with the title set to the current
542 system time, and set the page to expire on 1 Jan 2099."""
543
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000544 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000545 return False
546
547 self.send_response(200)
548 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000549 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000550 self.end_headers()
551
maruel@google.come250a9b2009-03-10 17:39:46 +0000552 self.wfile.write('<html><head><title>%s</title></head></html>' %
553 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000554
555 return True
556
557 def CacheProxyRevalidateHandler(self):
558 """This request handler yields a page with the title set to the current
559 system time, and allows caching for 60 seconds"""
560
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000561 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000562 return False
563
564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000565 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000566 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
567 self.end_headers()
568
maruel@google.come250a9b2009-03-10 17:39:46 +0000569 self.wfile.write('<html><head><title>%s</title></head></html>' %
570 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000571
572 return True
573
574 def CachePrivateHandler(self):
575 """This request handler yields a page with the title set to the current
576 system time, and allows caching for 5 seconds."""
577
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000578 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000579 return False
580
581 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000582 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000583 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000584 self.end_headers()
585
maruel@google.come250a9b2009-03-10 17:39:46 +0000586 self.wfile.write('<html><head><title>%s</title></head></html>' %
587 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000588
589 return True
590
591 def CachePublicHandler(self):
592 """This request handler yields a page with the title set to the current
593 system time, and allows caching for 5 seconds."""
594
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000595 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000596 return False
597
598 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000599 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000600 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000601 self.end_headers()
602
maruel@google.come250a9b2009-03-10 17:39:46 +0000603 self.wfile.write('<html><head><title>%s</title></head></html>' %
604 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000605
606 return True
607
608 def CacheSMaxAgeHandler(self):
609 """This request handler yields a page with the title set to the current
610 system time, and does not allow for caching."""
611
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000612 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000613 return False
614
615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000616 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000617 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
618 self.end_headers()
619
maruel@google.come250a9b2009-03-10 17:39:46 +0000620 self.wfile.write('<html><head><title>%s</title></head></html>' %
621 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000622
623 return True
624
625 def CacheMustRevalidateHandler(self):
626 """This request handler yields a page with the title set to the current
627 system time, and does not allow caching."""
628
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000629 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000630 return False
631
632 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000633 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000634 self.send_header('Cache-Control', 'must-revalidate')
635 self.end_headers()
636
maruel@google.come250a9b2009-03-10 17:39:46 +0000637 self.wfile.write('<html><head><title>%s</title></head></html>' %
638 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000639
640 return True
641
642 def CacheMustRevalidateMaxAgeHandler(self):
643 """This request handler yields a page with the title set to the current
644 system time, and does not allow caching event though max-age of 60
645 seconds is specified."""
646
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000647 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000648 return False
649
650 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000651 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000652 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
653 self.end_headers()
654
maruel@google.come250a9b2009-03-10 17:39:46 +0000655 self.wfile.write('<html><head><title>%s</title></head></html>' %
656 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000657
658 return True
659
initial.commit94958cf2008-07-26 22:42:52 +0000660 def CacheNoStoreHandler(self):
661 """This request handler yields a page with the title set to the current
662 system time, and does not allow the page to be stored."""
663
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000664 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000665 return False
666
667 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000668 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000669 self.send_header('Cache-Control', 'no-store')
670 self.end_headers()
671
maruel@google.come250a9b2009-03-10 17:39:46 +0000672 self.wfile.write('<html><head><title>%s</title></head></html>' %
673 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000674
675 return True
676
677 def CacheNoStoreMaxAgeHandler(self):
678 """This request handler yields a page with the title set to the current
679 system time, and does not allow the page to be stored even though max-age
680 of 60 seconds is specified."""
681
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000682 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000683 return False
684
685 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000686 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000687 self.send_header('Cache-Control', 'max-age=60, no-store')
688 self.end_headers()
689
maruel@google.come250a9b2009-03-10 17:39:46 +0000690 self.wfile.write('<html><head><title>%s</title></head></html>' %
691 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000692
693 return True
694
695
696 def CacheNoTransformHandler(self):
697 """This request handler yields a page with the title set to the current
698 system time, and does not allow the content to transformed during
699 user-agent caching"""
700
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000701 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000702 return False
703
704 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000705 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000706 self.send_header('Cache-Control', 'no-transform')
707 self.end_headers()
708
maruel@google.come250a9b2009-03-10 17:39:46 +0000709 self.wfile.write('<html><head><title>%s</title></head></html>' %
710 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000711
712 return True
713
714 def EchoHeader(self):
715 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000716
ananta@chromium.org219b2062009-10-23 16:09:41 +0000717 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000718
ananta@chromium.org56812d02011-04-07 17:52:05 +0000719 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000720 """This function echoes back the value of a specific request header while
721 allowing caching for 16 hours."""
722
ananta@chromium.org56812d02011-04-07 17:52:05 +0000723 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000724
725 def EchoHeaderHelper(self, echo_header):
726 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000727
ananta@chromium.org219b2062009-10-23 16:09:41 +0000728 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000729 return False
730
731 query_char = self.path.find('?')
732 if query_char != -1:
733 header_name = self.path[query_char+1:]
734
735 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000736 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000737 if echo_header == '/echoheadercache':
738 self.send_header('Cache-control', 'max-age=60000')
739 else:
740 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000741 # insert a vary header to properly indicate that the cachability of this
742 # request is subject to value of the request header being echoed.
743 if len(header_name) > 0:
744 self.send_header('Vary', header_name)
745 self.end_headers()
746
747 if len(header_name) > 0:
748 self.wfile.write(self.headers.getheader(header_name))
749
750 return True
751
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000752 def ReadRequestBody(self):
753 """This function reads the body of the current HTTP request, handling
754 both plain and chunked transfer encoded requests."""
755
756 if self.headers.getheader('transfer-encoding') != 'chunked':
757 length = int(self.headers.getheader('content-length'))
758 return self.rfile.read(length)
759
760 # Read the request body as chunks.
761 body = ""
762 while True:
763 line = self.rfile.readline()
764 length = int(line, 16)
765 if length == 0:
766 self.rfile.readline()
767 break
768 body += self.rfile.read(length)
769 self.rfile.read(2)
770 return body
771
initial.commit94958cf2008-07-26 22:42:52 +0000772 def EchoHandler(self):
773 """This handler just echoes back the payload of the request, for testing
774 form submission."""
775
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000776 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000777 return False
778
779 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000780 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000781 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000782 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000783 return True
784
785 def EchoTitleHandler(self):
786 """This handler is like Echo, but sets the page title to the request."""
787
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000788 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000789 return False
790
791 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000792 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000793 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000794 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000795 self.wfile.write('<html><head><title>')
796 self.wfile.write(request)
797 self.wfile.write('</title></head></html>')
798 return True
799
800 def EchoAllHandler(self):
801 """This handler yields a (more) human-readable page listing information
802 about the request header & contents."""
803
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000804 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000805 return False
806
807 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000808 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000809 self.end_headers()
810 self.wfile.write('<html><head><style>'
811 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
812 '</style></head><body>'
813 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000814 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000815 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000816
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000817 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000818 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000819 params = cgi.parse_qs(qs, keep_blank_values=1)
820
821 for param in params:
822 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000823
824 self.wfile.write('</pre>')
825
826 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
827
828 self.wfile.write('</body></html>')
829 return True
830
831 def DownloadHandler(self):
832 """This handler sends a downloadable file with or without reporting
833 the size (6K)."""
834
835 if self.path.startswith("/download-unknown-size"):
836 send_length = False
837 elif self.path.startswith("/download-known-size"):
838 send_length = True
839 else:
840 return False
841
842 #
843 # The test which uses this functionality is attempting to send
844 # small chunks of data to the client. Use a fairly large buffer
845 # so that we'll fill chrome's IO buffer enough to force it to
846 # actually write the data.
847 # See also the comments in the client-side of this test in
848 # download_uitest.cc
849 #
850 size_chunk1 = 35*1024
851 size_chunk2 = 10*1024
852
853 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000854 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000855 self.send_header('Cache-Control', 'max-age=0')
856 if send_length:
857 self.send_header('Content-Length', size_chunk1 + size_chunk2)
858 self.end_headers()
859
860 # First chunk of data:
861 self.wfile.write("*" * size_chunk1)
862 self.wfile.flush()
863
864 # handle requests until one of them clears this flag.
865 self.server.waitForDownload = True
866 while self.server.waitForDownload:
867 self.server.handle_request()
868
869 # Second chunk of data:
870 self.wfile.write("*" * size_chunk2)
871 return True
872
873 def DownloadFinishHandler(self):
874 """This handler just tells the server to finish the current download."""
875
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000876 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000877 return False
878
879 self.server.waitForDownload = False
880 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000881 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000882 self.send_header('Cache-Control', 'max-age=0')
883 self.end_headers()
884 return True
885
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000886 def _ReplaceFileData(self, data, query_parameters):
887 """Replaces matching substrings in a file.
888
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000889 If the 'replace_text' URL query parameter is present, it is expected to be
890 of the form old_text:new_text, which indicates that any old_text strings in
891 the file are replaced with new_text. Multiple 'replace_text' parameters may
892 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000893
894 If the parameters are not present, |data| is returned.
895 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000896
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000897 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000898 replace_text_values = query_dict.get('replace_text', [])
899 for replace_text_value in replace_text_values:
900 replace_text_args = replace_text_value.split(':')
901 if len(replace_text_args) != 2:
902 raise ValueError(
903 'replace_text must be of form old_text:new_text. Actual value: %s' %
904 replace_text_value)
905 old_text_b64, new_text_b64 = replace_text_args
906 old_text = base64.urlsafe_b64decode(old_text_b64)
907 new_text = base64.urlsafe_b64decode(new_text_b64)
908 data = data.replace(old_text, new_text)
909 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000910
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000911 def ZipFileHandler(self):
912 """This handler sends the contents of the requested file in compressed form.
913 Can pass in a parameter that specifies that the content length be
914 C - the compressed size (OK),
915 U - the uncompressed size (Non-standard, but handled),
916 S - less than compressed (OK because we keep going),
917 M - larger than compressed but less than uncompressed (an error),
918 L - larger than uncompressed (an error)
919 Example: compressedfiles/Picture_1.doc?C
920 """
921
922 prefix = "/compressedfiles/"
923 if not self.path.startswith(prefix):
924 return False
925
926 # Consume a request body if present.
927 if self.command == 'POST' or self.command == 'PUT' :
928 self.ReadRequestBody()
929
930 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
931
932 if not query in ('C', 'U', 'S', 'M', 'L'):
933 return False
934
935 sub_path = url_path[len(prefix):]
936 entries = sub_path.split('/')
937 file_path = os.path.join(self.server.data_dir, *entries)
938 if os.path.isdir(file_path):
939 file_path = os.path.join(file_path, 'index.html')
940
941 if not os.path.isfile(file_path):
942 print "File not found " + sub_path + " full path:" + file_path
943 self.send_error(404)
944 return True
945
946 f = open(file_path, "rb")
947 data = f.read()
948 uncompressed_len = len(data)
949 f.close()
950
951 # Compress the data.
952 data = zlib.compress(data)
953 compressed_len = len(data)
954
955 content_length = compressed_len
956 if query == 'U':
957 content_length = uncompressed_len
958 elif query == 'S':
959 content_length = compressed_len / 2
960 elif query == 'M':
961 content_length = (compressed_len + uncompressed_len) / 2
962 elif query == 'L':
963 content_length = compressed_len + uncompressed_len
964
965 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000966 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000967 self.send_header('Content-encoding', 'deflate')
968 self.send_header('Connection', 'close')
969 self.send_header('Content-Length', content_length)
970 self.send_header('ETag', '\'' + file_path + '\'')
971 self.end_headers()
972
973 self.wfile.write(data)
974
975 return True
976
initial.commit94958cf2008-07-26 22:42:52 +0000977 def FileHandler(self):
978 """This handler sends the contents of the requested file. Wow, it's like
979 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000980
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000981 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000982 if not self.path.startswith(prefix):
983 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000984 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000985
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000986 def PostOnlyFileHandler(self):
987 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000988
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000989 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000990 if not self.path.startswith(prefix):
991 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000992 return self._FileHandlerHelper(prefix)
993
994 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000995 request_body = ''
996 if self.command == 'POST' or self.command == 'PUT':
997 # Consume a request body if present.
998 request_body = self.ReadRequestBody()
999
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001000 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001001 query_dict = cgi.parse_qs(query)
1002
1003 expected_body = query_dict.get('expected_body', [])
1004 if expected_body and request_body not in expected_body:
1005 self.send_response(404)
1006 self.end_headers()
1007 self.wfile.write('')
1008 return True
1009
1010 expected_headers = query_dict.get('expected_headers', [])
1011 for expected_header in expected_headers:
1012 header_name, expected_value = expected_header.split(':')
1013 if self.headers.getheader(header_name) != expected_value:
1014 self.send_response(404)
1015 self.end_headers()
1016 self.wfile.write('')
1017 return True
1018
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001019 sub_path = url_path[len(prefix):]
1020 entries = sub_path.split('/')
1021 file_path = os.path.join(self.server.data_dir, *entries)
1022 if os.path.isdir(file_path):
1023 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001024
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001025 if not os.path.isfile(file_path):
1026 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001027 self.send_error(404)
1028 return True
1029
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001030 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001031 data = f.read()
1032 f.close()
1033
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001034 data = self._ReplaceFileData(data, query)
1035
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001036 old_protocol_version = self.protocol_version
1037
initial.commit94958cf2008-07-26 22:42:52 +00001038 # If file.mock-http-headers exists, it contains the headers we
1039 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001040 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001041 if os.path.isfile(headers_path):
1042 f = open(headers_path, "r")
1043
1044 # "HTTP/1.1 200 OK"
1045 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001046 http_major, http_minor, status_code = re.findall(
1047 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1048 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001049 self.send_response(int(status_code))
1050
1051 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001052 header_values = re.findall('(\S+):\s*(.*)', line)
1053 if len(header_values) > 0:
1054 # "name: value"
1055 name, value = header_values[0]
1056 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001057 f.close()
1058 else:
1059 # Could be more generic once we support mime-type sniffing, but for
1060 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001061
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001062 range_header = self.headers.get('Range')
1063 if range_header and range_header.startswith('bytes='):
1064 # Note this doesn't handle all valid byte range_header values (i.e.
1065 # left open ended ones), just enough for what we needed so far.
1066 range_header = range_header[6:].split('-')
1067 start = int(range_header[0])
1068 if range_header[1]:
1069 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001070 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001071 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001072
1073 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001074 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1075 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001076 self.send_header('Content-Range', content_range)
1077 data = data[start: end + 1]
1078 else:
1079 self.send_response(200)
1080
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001081 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001082 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001083 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001084 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001085 self.end_headers()
1086
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001087 if (self.command != 'HEAD'):
1088 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001089
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001090 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001091 return True
1092
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001093 def SetCookieHandler(self):
1094 """This handler just sets a cookie, for testing cookie handling."""
1095
1096 if not self._ShouldHandleRequest("/set-cookie"):
1097 return False
1098
1099 query_char = self.path.find('?')
1100 if query_char != -1:
1101 cookie_values = self.path[query_char + 1:].split('&')
1102 else:
1103 cookie_values = ("",)
1104 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001105 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001106 for cookie_value in cookie_values:
1107 self.send_header('Set-Cookie', '%s' % cookie_value)
1108 self.end_headers()
1109 for cookie_value in cookie_values:
1110 self.wfile.write('%s' % cookie_value)
1111 return True
1112
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001113 def SetManyCookiesHandler(self):
1114 """This handler just sets a given number of cookies, for testing handling
1115 of large numbers of cookies."""
1116
1117 if not self._ShouldHandleRequest("/set-many-cookies"):
1118 return False
1119
1120 query_char = self.path.find('?')
1121 if query_char != -1:
1122 num_cookies = int(self.path[query_char + 1:])
1123 else:
1124 num_cookies = 0
1125 self.send_response(200)
1126 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001127 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001128 self.send_header('Set-Cookie', 'a=')
1129 self.end_headers()
1130 self.wfile.write('%d cookies were sent' % num_cookies)
1131 return True
1132
mattm@chromium.org983fc462012-06-30 00:52:08 +00001133 def ExpectAndSetCookieHandler(self):
1134 """Expects some cookies to be sent, and if they are, sets more cookies.
1135
1136 The expect parameter specifies a required cookie. May be specified multiple
1137 times.
1138 The set parameter specifies a cookie to set if all required cookies are
1139 preset. May be specified multiple times.
1140 The data parameter specifies the response body data to be returned."""
1141
1142 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1143 return False
1144
1145 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1146 query_dict = cgi.parse_qs(query)
1147 cookies = set()
1148 if 'Cookie' in self.headers:
1149 cookie_header = self.headers.getheader('Cookie')
1150 cookies.update([s.strip() for s in cookie_header.split(';')])
1151 got_all_expected_cookies = True
1152 for expected_cookie in query_dict.get('expect', []):
1153 if expected_cookie not in cookies:
1154 got_all_expected_cookies = False
1155 self.send_response(200)
1156 self.send_header('Content-Type', 'text/html')
1157 if got_all_expected_cookies:
1158 for cookie_value in query_dict.get('set', []):
1159 self.send_header('Set-Cookie', '%s' % cookie_value)
1160 self.end_headers()
1161 for data_value in query_dict.get('data', []):
1162 self.wfile.write(data_value)
1163 return True
1164
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001165 def SetHeaderHandler(self):
1166 """This handler sets a response header. Parameters are in the
1167 key%3A%20value&key2%3A%20value2 format."""
1168
1169 if not self._ShouldHandleRequest("/set-header"):
1170 return False
1171
1172 query_char = self.path.find('?')
1173 if query_char != -1:
1174 headers_values = self.path[query_char + 1:].split('&')
1175 else:
1176 headers_values = ("",)
1177 self.send_response(200)
1178 self.send_header('Content-Type', 'text/html')
1179 for header_value in headers_values:
1180 header_value = urllib.unquote(header_value)
1181 (key, value) = header_value.split(': ', 1)
1182 self.send_header(key, value)
1183 self.end_headers()
1184 for header_value in headers_values:
1185 self.wfile.write('%s' % header_value)
1186 return True
1187
initial.commit94958cf2008-07-26 22:42:52 +00001188 def AuthBasicHandler(self):
1189 """This handler tests 'Basic' authentication. It just sends a page with
1190 title 'user/pass' if you succeed."""
1191
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001192 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001193 return False
1194
1195 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001196 expected_password = 'secret'
1197 realm = 'testrealm'
1198 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001199
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001200 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1201 query_params = cgi.parse_qs(query, True)
1202 if 'set-cookie-if-challenged' in query_params:
1203 set_cookie_if_challenged = True
1204 if 'password' in query_params:
1205 expected_password = query_params['password'][0]
1206 if 'realm' in query_params:
1207 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001208
initial.commit94958cf2008-07-26 22:42:52 +00001209 auth = self.headers.getheader('authorization')
1210 try:
1211 if not auth:
1212 raise Exception('no auth')
1213 b64str = re.findall(r'Basic (\S+)', auth)[0]
1214 userpass = base64.b64decode(b64str)
1215 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001216 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001217 raise Exception('wrong password')
1218 except Exception, e:
1219 # Authentication failed.
1220 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001221 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001222 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001223 if set_cookie_if_challenged:
1224 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001225 self.end_headers()
1226 self.wfile.write('<html><head>')
1227 self.wfile.write('<title>Denied: %s</title>' % e)
1228 self.wfile.write('</head><body>')
1229 self.wfile.write('auth=%s<p>' % auth)
1230 self.wfile.write('b64str=%s<p>' % b64str)
1231 self.wfile.write('username: %s<p>' % username)
1232 self.wfile.write('userpass: %s<p>' % userpass)
1233 self.wfile.write('password: %s<p>' % password)
1234 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1235 self.wfile.write('</body></html>')
1236 return True
1237
1238 # Authentication successful. (Return a cachable response to allow for
1239 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001240 old_protocol_version = self.protocol_version
1241 self.protocol_version = "HTTP/1.1"
1242
initial.commit94958cf2008-07-26 22:42:52 +00001243 if_none_match = self.headers.getheader('if-none-match')
1244 if if_none_match == "abc":
1245 self.send_response(304)
1246 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001247 elif url_path.endswith(".gif"):
1248 # Using chrome/test/data/google/logo.gif as the test image
1249 test_image_path = ['google', 'logo.gif']
1250 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1251 if not os.path.isfile(gif_path):
1252 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001253 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001254 return True
1255
1256 f = open(gif_path, "rb")
1257 data = f.read()
1258 f.close()
1259
1260 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001261 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001262 self.send_header('Cache-control', 'max-age=60000')
1263 self.send_header('Etag', 'abc')
1264 self.end_headers()
1265 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001266 else:
1267 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001268 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001269 self.send_header('Cache-control', 'max-age=60000')
1270 self.send_header('Etag', 'abc')
1271 self.end_headers()
1272 self.wfile.write('<html><head>')
1273 self.wfile.write('<title>%s/%s</title>' % (username, password))
1274 self.wfile.write('</head><body>')
1275 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001276 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001277 self.wfile.write('</body></html>')
1278
rvargas@google.com54453b72011-05-19 01:11:11 +00001279 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001280 return True
1281
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001282 def GDataAuthHandler(self):
1283 """This handler verifies the Authentication header for GData requests."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001284
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001285 if not self.server.gdata_auth_token:
1286 # --auth-token is not specified, not the test case for GData.
1287 return False
1288
1289 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1290 return False
1291
1292 if 'GData-Version' not in self.headers:
1293 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1294 return True
1295
1296 if 'Authorization' not in self.headers:
1297 self.send_error(httplib.UNAUTHORIZED)
1298 return True
1299
1300 field_prefix = 'Bearer '
1301 authorization = self.headers['Authorization']
1302 if not authorization.startswith(field_prefix):
1303 self.send_error(httplib.UNAUTHORIZED)
1304 return True
1305
1306 code = authorization[len(field_prefix):]
1307 if code != self.server.gdata_auth_token:
1308 self.send_error(httplib.UNAUTHORIZED)
1309 return True
1310
1311 return False
1312
1313 def GDataDocumentsFeedQueryHandler(self):
1314 """This handler verifies if required parameters are properly
1315 specified for the GData DocumentsFeed request."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001316
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001317 if not self.server.gdata_auth_token:
1318 # --auth-token is not specified, not the test case for GData.
1319 return False
1320
1321 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1322 return False
1323
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001324 (_path, _question, query_params) = self.path.partition('?')
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001325 self.query_params = urlparse.parse_qs(query_params)
1326
1327 if 'v' not in self.query_params:
1328 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1329 return True
1330 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1331 # currently our GData client only uses JSON format.
1332 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1333 return True
1334
1335 return False
1336
tonyg@chromium.org75054202010-03-31 22:06:10 +00001337 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001338 """Returns a nonce that's stable per request path for the server's lifetime.
1339 This is a fake implementation. A real implementation would only use a given
1340 nonce a single time (hence the name n-once). However, for the purposes of
1341 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001342
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001343 Args:
1344 force_reset: Iff set, the nonce will be changed. Useful for testing the
1345 "stale" response.
1346 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001347
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001348 if force_reset or not self.server.nonce_time:
1349 self.server.nonce_time = time.time()
1350 return hashlib.md5('privatekey%s%d' %
1351 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001352
1353 def AuthDigestHandler(self):
1354 """This handler tests 'Digest' authentication.
1355
1356 It just sends a page with title 'user/pass' if you succeed.
1357
1358 A stale response is sent iff "stale" is present in the request path.
1359 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001360
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001361 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001362 return False
1363
tonyg@chromium.org75054202010-03-31 22:06:10 +00001364 stale = 'stale' in self.path
1365 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001366 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001367 password = 'secret'
1368 realm = 'testrealm'
1369
1370 auth = self.headers.getheader('authorization')
1371 pairs = {}
1372 try:
1373 if not auth:
1374 raise Exception('no auth')
1375 if not auth.startswith('Digest'):
1376 raise Exception('not digest')
1377 # Pull out all the name="value" pairs as a dictionary.
1378 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1379
1380 # Make sure it's all valid.
1381 if pairs['nonce'] != nonce:
1382 raise Exception('wrong nonce')
1383 if pairs['opaque'] != opaque:
1384 raise Exception('wrong opaque')
1385
1386 # Check the 'response' value and make sure it matches our magic hash.
1387 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001388 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001389 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001390 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001391 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001392 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001393 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1394 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001395 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001396
1397 if pairs['response'] != response:
1398 raise Exception('wrong password')
1399 except Exception, e:
1400 # Authentication failed.
1401 self.send_response(401)
1402 hdr = ('Digest '
1403 'realm="%s", '
1404 'domain="/", '
1405 'qop="auth", '
1406 'algorithm=MD5, '
1407 'nonce="%s", '
1408 'opaque="%s"') % (realm, nonce, opaque)
1409 if stale:
1410 hdr += ', stale="TRUE"'
1411 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001412 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001413 self.end_headers()
1414 self.wfile.write('<html><head>')
1415 self.wfile.write('<title>Denied: %s</title>' % e)
1416 self.wfile.write('</head><body>')
1417 self.wfile.write('auth=%s<p>' % auth)
1418 self.wfile.write('pairs=%s<p>' % pairs)
1419 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1420 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1421 self.wfile.write('</body></html>')
1422 return True
1423
1424 # Authentication successful.
1425 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001426 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001427 self.end_headers()
1428 self.wfile.write('<html><head>')
1429 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1430 self.wfile.write('</head><body>')
1431 self.wfile.write('auth=%s<p>' % auth)
1432 self.wfile.write('pairs=%s<p>' % pairs)
1433 self.wfile.write('</body></html>')
1434
1435 return True
1436
1437 def SlowServerHandler(self):
1438 """Wait for the user suggested time before responding. The syntax is
1439 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001440
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001441 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001442 return False
1443 query_char = self.path.find('?')
1444 wait_sec = 1.0
1445 if query_char >= 0:
1446 try:
1447 wait_sec = int(self.path[query_char + 1:])
1448 except ValueError:
1449 pass
1450 time.sleep(wait_sec)
1451 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001452 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001453 self.end_headers()
1454 self.wfile.write("waited %d seconds" % wait_sec)
1455 return True
1456
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001457 def ChunkedServerHandler(self):
1458 """Send chunked response. Allows to specify chunks parameters:
1459 - waitBeforeHeaders - ms to wait before sending headers
1460 - waitBetweenChunks - ms to wait between chunks
1461 - chunkSize - size of each chunk in bytes
1462 - chunksNumber - number of chunks
1463 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1464 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001465
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001466 if not self._ShouldHandleRequest("/chunked"):
1467 return False
1468 query_char = self.path.find('?')
1469 chunkedSettings = {'waitBeforeHeaders' : 0,
1470 'waitBetweenChunks' : 0,
1471 'chunkSize' : 5,
1472 'chunksNumber' : 5}
1473 if query_char >= 0:
1474 params = self.path[query_char + 1:].split('&')
1475 for param in params:
1476 keyValue = param.split('=')
1477 if len(keyValue) == 2:
1478 try:
1479 chunkedSettings[keyValue[0]] = int(keyValue[1])
1480 except ValueError:
1481 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001482 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001483 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1484 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001485 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001486 self.send_header('Connection', 'close')
1487 self.send_header('Transfer-Encoding', 'chunked')
1488 self.end_headers()
1489 # Chunked encoding: sending all chunks, then final zero-length chunk and
1490 # then final CRLF.
1491 for i in range(0, chunkedSettings['chunksNumber']):
1492 if i > 0:
1493 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1494 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001495 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001496 self.sendChunkHelp('')
1497 return True
1498
initial.commit94958cf2008-07-26 22:42:52 +00001499 def ContentTypeHandler(self):
1500 """Returns a string of html with the given content type. E.g.,
1501 /contenttype?text/css returns an html file with the Content-Type
1502 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001503
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001504 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001505 return False
1506 query_char = self.path.find('?')
1507 content_type = self.path[query_char + 1:].strip()
1508 if not content_type:
1509 content_type = 'text/html'
1510 self.send_response(200)
1511 self.send_header('Content-Type', content_type)
1512 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001513 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001514 return True
1515
creis@google.com2f4f6a42011-03-25 19:44:19 +00001516 def NoContentHandler(self):
1517 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001518
creis@google.com2f4f6a42011-03-25 19:44:19 +00001519 if not self._ShouldHandleRequest("/nocontent"):
1520 return False
1521 self.send_response(204)
1522 self.end_headers()
1523 return True
1524
initial.commit94958cf2008-07-26 22:42:52 +00001525 def ServerRedirectHandler(self):
1526 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001527 '/server-redirect?http://foo.bar/asdf' to redirect to
1528 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001529
1530 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001531 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001532 return False
1533
1534 query_char = self.path.find('?')
1535 if query_char < 0 or len(self.path) <= query_char + 1:
1536 self.sendRedirectHelp(test_name)
1537 return True
1538 dest = self.path[query_char + 1:]
1539
1540 self.send_response(301) # moved permanently
1541 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001542 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001543 self.end_headers()
1544 self.wfile.write('<html><head>')
1545 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1546
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001547 return True
initial.commit94958cf2008-07-26 22:42:52 +00001548
1549 def ClientRedirectHandler(self):
1550 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001551 '/client-redirect?http://foo.bar/asdf' to redirect to
1552 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001553
1554 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001555 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001556 return False
1557
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001558 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001559 if query_char < 0 or len(self.path) <= query_char + 1:
1560 self.sendRedirectHelp(test_name)
1561 return True
1562 dest = self.path[query_char + 1:]
1563
1564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001565 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001566 self.end_headers()
1567 self.wfile.write('<html><head>')
1568 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1569 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1570
1571 return True
1572
tony@chromium.org03266982010-03-05 03:18:42 +00001573 def MultipartHandler(self):
1574 """Send a multipart response (10 text/html pages)."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001575
tony@chromium.org4cb88302011-09-27 22:13:49 +00001576 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001577 if not self._ShouldHandleRequest(test_name):
1578 return False
1579
1580 num_frames = 10
1581 bound = '12345'
1582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001583 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001584 'multipart/x-mixed-replace;boundary=' + bound)
1585 self.end_headers()
1586
1587 for i in xrange(num_frames):
1588 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001589 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001590 self.wfile.write('<title>page ' + str(i) + '</title>')
1591 self.wfile.write('page ' + str(i))
1592
1593 self.wfile.write('--' + bound + '--')
1594 return True
1595
tony@chromium.org4cb88302011-09-27 22:13:49 +00001596 def MultipartSlowHandler(self):
1597 """Send a multipart response (3 text/html pages) with a slight delay
1598 between each page. This is similar to how some pages show status using
1599 multipart."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001600
tony@chromium.org4cb88302011-09-27 22:13:49 +00001601 test_name = '/multipart-slow'
1602 if not self._ShouldHandleRequest(test_name):
1603 return False
1604
1605 num_frames = 3
1606 bound = '12345'
1607 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001608 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001609 'multipart/x-mixed-replace;boundary=' + bound)
1610 self.end_headers()
1611
1612 for i in xrange(num_frames):
1613 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001614 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001615 time.sleep(0.25)
1616 if i == 2:
1617 self.wfile.write('<title>PASS</title>')
1618 else:
1619 self.wfile.write('<title>page ' + str(i) + '</title>')
1620 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1621
1622 self.wfile.write('--' + bound + '--')
1623 return True
1624
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001625 def GetSSLSessionCacheHandler(self):
1626 """Send a reply containing a log of the session cache operations."""
1627
1628 if not self._ShouldHandleRequest('/ssl-session-cache'):
1629 return False
1630
1631 self.send_response(200)
1632 self.send_header('Content-Type', 'text/plain')
1633 self.end_headers()
1634 try:
1635 for (action, sessionID) in self.server.session_cache.log:
1636 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001637 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001638 self.wfile.write('Pass --https-record-resume in order to use' +
1639 ' this request')
1640 return True
1641
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001642 def CloseSocketHandler(self):
1643 """Closes the socket without sending anything."""
1644
1645 if not self._ShouldHandleRequest('/close-socket'):
1646 return False
1647
1648 self.wfile.close()
1649 return True
1650
initial.commit94958cf2008-07-26 22:42:52 +00001651 def DefaultResponseHandler(self):
1652 """This is the catch-all response handler for requests that aren't handled
1653 by one of the special handlers above.
1654 Note that we specify the content-length as without it the https connection
1655 is not closed properly (and the browser keeps expecting data)."""
1656
1657 contents = "Default response given for path: " + self.path
1658 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001659 self.send_header('Content-Type', 'text/html')
1660 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001661 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 if (self.command != 'HEAD'):
1663 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001664 return True
1665
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001666 def RedirectConnectHandler(self):
1667 """Sends a redirect to the CONNECT request for www.redirect.com. This
1668 response is not specified by the RFC, so the browser should not follow
1669 the redirect."""
1670
1671 if (self.path.find("www.redirect.com") < 0):
1672 return False
1673
1674 dest = "http://www.destination.com/foo.js"
1675
1676 self.send_response(302) # moved temporarily
1677 self.send_header('Location', dest)
1678 self.send_header('Connection', 'close')
1679 self.end_headers()
1680 return True
1681
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001682 def ServerAuthConnectHandler(self):
1683 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1684 response doesn't make sense because the proxy server cannot request
1685 server authentication."""
1686
1687 if (self.path.find("www.server-auth.com") < 0):
1688 return False
1689
1690 challenge = 'Basic realm="WallyWorld"'
1691
1692 self.send_response(401) # unauthorized
1693 self.send_header('WWW-Authenticate', challenge)
1694 self.send_header('Connection', 'close')
1695 self.end_headers()
1696 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001697
1698 def DefaultConnectResponseHandler(self):
1699 """This is the catch-all response handler for CONNECT requests that aren't
1700 handled by one of the special handlers above. Real Web servers respond
1701 with 400 to CONNECT requests."""
1702
1703 contents = "Your client has issued a malformed or illegal request."
1704 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001705 self.send_header('Content-Type', 'text/html')
1706 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001707 self.end_headers()
1708 self.wfile.write(contents)
1709 return True
1710
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001711 def DeviceManagementHandler(self):
1712 """Delegates to the device management service used for cloud policy."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001713
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001714 if not self._ShouldHandleRequest("/device_management"):
1715 return False
1716
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001717 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001718
1719 if not self.server._device_management_handler:
1720 import device_management
1721 policy_path = os.path.join(self.server.data_dir, 'device_management')
1722 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001723 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001724 self.server.policy_keys,
1725 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001726
1727 http_response, raw_reply = (
1728 self.server._device_management_handler.HandleRequest(self.path,
1729 self.headers,
1730 raw_request))
1731 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001732 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001733 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001734 self.end_headers()
1735 self.wfile.write(raw_reply)
1736 return True
1737
initial.commit94958cf2008-07-26 22:42:52 +00001738 # called by the redirect handling function when there is no parameter
1739 def sendRedirectHelp(self, redirect_name):
1740 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001741 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001742 self.end_headers()
1743 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1744 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1745 self.wfile.write('</body></html>')
1746
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001747 # called by chunked handling function
1748 def sendChunkHelp(self, chunk):
1749 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1750 self.wfile.write('%X\r\n' % len(chunk))
1751 self.wfile.write(chunk)
1752 self.wfile.write('\r\n')
1753
akalin@chromium.org154bb132010-11-12 02:20:27 +00001754
1755class SyncPageHandler(BasePageHandler):
1756 """Handler for the main HTTP sync server."""
1757
1758 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001759 get_handlers = [self.ChromiumSyncTimeHandler,
1760 self.ChromiumSyncMigrationOpHandler,
1761 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001762 self.ChromiumSyncDisableNotificationsOpHandler,
1763 self.ChromiumSyncEnableNotificationsOpHandler,
1764 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001765 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001766 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001767 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001768 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001769 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001770
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001771 post_handlers = [self.ChromiumSyncCommandHandler,
1772 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001773 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001774 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001775 post_handlers, [])
1776
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001777
akalin@chromium.org154bb132010-11-12 02:20:27 +00001778 def ChromiumSyncTimeHandler(self):
1779 """Handle Chromium sync .../time requests.
1780
1781 The syncer sometimes checks server reachability by examining /time.
1782 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001783
akalin@chromium.org154bb132010-11-12 02:20:27 +00001784 test_name = "/chromiumsync/time"
1785 if not self._ShouldHandleRequest(test_name):
1786 return False
1787
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001788 # Chrome hates it if we send a response before reading the request.
1789 if self.headers.getheader('content-length'):
1790 length = int(self.headers.getheader('content-length'))
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001791 _raw_request = self.rfile.read(length)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001792
akalin@chromium.org154bb132010-11-12 02:20:27 +00001793 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001794 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001795 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001796 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001797 return True
1798
1799 def ChromiumSyncCommandHandler(self):
1800 """Handle a chromiumsync command arriving via http.
1801
1802 This covers all sync protocol commands: authentication, getupdates, and
1803 commit.
1804 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001805
akalin@chromium.org154bb132010-11-12 02:20:27 +00001806 test_name = "/chromiumsync/command"
1807 if not self._ShouldHandleRequest(test_name):
1808 return False
1809
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001810 length = int(self.headers.getheader('content-length'))
1811 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001812 http_response = 200
1813 raw_reply = None
1814 if not self.server.GetAuthenticated():
1815 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001816 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1817 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001818 else:
1819 http_response, raw_reply = self.server.HandleCommand(
1820 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001821
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001822 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001823 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001824 if http_response == 401:
1825 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001826 self.end_headers()
1827 self.wfile.write(raw_reply)
1828 return True
1829
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001830 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001831 test_name = "/chromiumsync/migrate"
1832 if not self._ShouldHandleRequest(test_name):
1833 return False
1834
1835 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1836 self.path)
1837 self.send_response(http_response)
1838 self.send_header('Content-Type', 'text/html')
1839 self.send_header('Content-Length', len(raw_reply))
1840 self.end_headers()
1841 self.wfile.write(raw_reply)
1842 return True
1843
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001844 def ChromiumSyncCredHandler(self):
1845 test_name = "/chromiumsync/cred"
1846 if not self._ShouldHandleRequest(test_name):
1847 return False
1848 try:
1849 query = urlparse.urlparse(self.path)[4]
1850 cred_valid = urlparse.parse_qs(query)['valid']
1851 if cred_valid[0] == 'True':
1852 self.server.SetAuthenticated(True)
1853 else:
1854 self.server.SetAuthenticated(False)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001855 except Exception:
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001856 self.server.SetAuthenticated(False)
1857
1858 http_response = 200
1859 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1860 self.send_response(http_response)
1861 self.send_header('Content-Type', 'text/html')
1862 self.send_header('Content-Length', len(raw_reply))
1863 self.end_headers()
1864 self.wfile.write(raw_reply)
1865 return True
1866
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001867 def ChromiumSyncDisableNotificationsOpHandler(self):
1868 test_name = "/chromiumsync/disablenotifications"
1869 if not self._ShouldHandleRequest(test_name):
1870 return False
1871 self.server.GetXmppServer().DisableNotifications()
1872 result = 200
1873 raw_reply = ('<html><title>Notifications disabled</title>'
1874 '<H1>Notifications disabled</H1></html>')
1875 self.send_response(result)
1876 self.send_header('Content-Type', 'text/html')
1877 self.send_header('Content-Length', len(raw_reply))
1878 self.end_headers()
1879 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001880 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001881
1882 def ChromiumSyncEnableNotificationsOpHandler(self):
1883 test_name = "/chromiumsync/enablenotifications"
1884 if not self._ShouldHandleRequest(test_name):
1885 return False
1886 self.server.GetXmppServer().EnableNotifications()
1887 result = 200
1888 raw_reply = ('<html><title>Notifications enabled</title>'
1889 '<H1>Notifications enabled</H1></html>')
1890 self.send_response(result)
1891 self.send_header('Content-Type', 'text/html')
1892 self.send_header('Content-Length', len(raw_reply))
1893 self.end_headers()
1894 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001895 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001896
1897 def ChromiumSyncSendNotificationOpHandler(self):
1898 test_name = "/chromiumsync/sendnotification"
1899 if not self._ShouldHandleRequest(test_name):
1900 return False
1901 query = urlparse.urlparse(self.path)[4]
1902 query_params = urlparse.parse_qs(query)
1903 channel = ''
1904 data = ''
1905 if 'channel' in query_params:
1906 channel = query_params['channel'][0]
1907 if 'data' in query_params:
1908 data = query_params['data'][0]
1909 self.server.GetXmppServer().SendNotification(channel, data)
1910 result = 200
1911 raw_reply = ('<html><title>Notification sent</title>'
1912 '<H1>Notification sent with channel "%s" '
1913 'and data "%s"</H1></html>'
1914 % (channel, data))
1915 self.send_response(result)
1916 self.send_header('Content-Type', 'text/html')
1917 self.send_header('Content-Length', len(raw_reply))
1918 self.end_headers()
1919 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001920 return True
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001921
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001922 def ChromiumSyncBirthdayErrorOpHandler(self):
1923 test_name = "/chromiumsync/birthdayerror"
1924 if not self._ShouldHandleRequest(test_name):
1925 return False
1926 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1927 self.send_response(result)
1928 self.send_header('Content-Type', 'text/html')
1929 self.send_header('Content-Length', len(raw_reply))
1930 self.end_headers()
1931 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001932 return True
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001933
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001934 def ChromiumSyncTransientErrorOpHandler(self):
1935 test_name = "/chromiumsync/transienterror"
1936 if not self._ShouldHandleRequest(test_name):
1937 return False
1938 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1939 self.send_response(result)
1940 self.send_header('Content-Type', 'text/html')
1941 self.send_header('Content-Length', len(raw_reply))
1942 self.end_headers()
1943 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001944 return True
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001945
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001946 def ChromiumSyncErrorOpHandler(self):
1947 test_name = "/chromiumsync/error"
1948 if not self._ShouldHandleRequest(test_name):
1949 return False
1950 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1951 self.path)
1952 self.send_response(result)
1953 self.send_header('Content-Type', 'text/html')
1954 self.send_header('Content-Length', len(raw_reply))
1955 self.end_headers()
1956 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001957 return True
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001958
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001959 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1960 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001961 if not self._ShouldHandleRequest(test_name):
1962 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001963 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001964 self.send_response(result)
1965 self.send_header('Content-Type', 'text/html')
1966 self.send_header('Content-Length', len(raw_reply))
1967 self.end_headers()
1968 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001969 return True
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001970
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001971 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1972 test_name = "/chromiumsync/createsyncedbookmarks"
1973 if not self._ShouldHandleRequest(test_name):
1974 return False
1975 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1976 self.send_response(result)
1977 self.send_header('Content-Type', 'text/html')
1978 self.send_header('Content-Length', len(raw_reply))
1979 self.end_headers()
1980 self.wfile.write(raw_reply)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001981 return True
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001982
akalin@chromium.org154bb132010-11-12 02:20:27 +00001983
newt@chromium.org1fc32742012-10-20 00:28:35 +00001984def MakeDataDir(options):
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001985 if options.data_dir:
1986 if not os.path.isdir(options.data_dir):
1987 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1988 return None
1989 my_data_dir = options.data_dir
1990 else:
1991 # Create the default path to our data dir, relative to the exe dir.
1992 my_data_dir = os.path.dirname(sys.argv[0])
1993 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1994 "test", "data")
1995
1996 #TODO(ibrar): Must use Find* funtion defined in google\tools
1997 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1998
1999 return my_data_dir
2000
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002001class OCSPHandler(BasePageHandler):
2002 def __init__(self, request, client_address, socket_server):
2003 handlers = [self.OCSPResponse]
2004 self.ocsp_response = socket_server.ocsp_response
2005 BasePageHandler.__init__(self, request, client_address, socket_server,
2006 [], handlers, [], handlers, [])
2007
2008 def OCSPResponse(self):
2009 self.send_response(200)
2010 self.send_header('Content-Type', 'application/ocsp-response')
2011 self.send_header('Content-Length', str(len(self.ocsp_response)))
2012 self.end_headers()
2013
2014 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002015
2016class TCPEchoHandler(SocketServer.BaseRequestHandler):
2017 """The RequestHandler class for TCP echo server.
2018
2019 It is instantiated once per connection to the server, and overrides the
2020 handle() method to implement communication to the client.
2021 """
2022
2023 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002024 """Handles the request from the client and constructs a response."""
2025
2026 data = self.request.recv(65536).strip()
2027 # Verify the "echo request" message received from the client. Send back
2028 # "echo response" message if "echo request" message is valid.
2029 try:
2030 return_data = echo_message.GetEchoResponseData(data)
2031 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002032 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002033 except ValueError:
2034 return
2035
2036 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002037
2038
2039class UDPEchoHandler(SocketServer.BaseRequestHandler):
2040 """The RequestHandler class for UDP echo server.
2041
2042 It is instantiated once per connection to the server, and overrides the
2043 handle() method to implement communication to the client.
2044 """
2045
2046 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002047 """Handles the request from the client and constructs a response."""
2048
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002049 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002050 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002051 # Verify the "echo request" message received from the client. Send back
2052 # "echo response" message if "echo request" message is valid.
2053 try:
2054 return_data = echo_message.GetEchoResponseData(data)
2055 if not return_data:
2056 return
2057 except ValueError:
2058 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002059 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002060
2061
bashi@chromium.org33233532012-09-08 17:37:24 +00002062class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2063 """A request handler that behaves as a proxy server which requires
2064 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2065 """
2066
2067 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2068
2069 def parse_request(self):
2070 """Overrides parse_request to check credential."""
2071
2072 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2073 return False
2074
2075 auth = self.headers.getheader('Proxy-Authorization')
2076 if auth != self._AUTH_CREDENTIAL:
2077 self.send_response(407)
2078 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2079 self.end_headers()
2080 return False
2081
2082 return True
2083
2084 def _start_read_write(self, sock):
2085 sock.setblocking(0)
2086 self.request.setblocking(0)
2087 rlist = [self.request, sock]
2088 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002089 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00002090 if errors:
2091 self.send_response(500)
2092 self.end_headers()
2093 return
2094 for s in ready_sockets:
2095 received = s.recv(1024)
2096 if len(received) == 0:
2097 return
2098 if s == self.request:
2099 other = sock
2100 else:
2101 other = self.request
2102 other.send(received)
2103
2104 def _do_common_method(self):
2105 url = urlparse.urlparse(self.path)
2106 port = url.port
2107 if not port:
2108 if url.scheme == 'http':
2109 port = 80
2110 elif url.scheme == 'https':
2111 port = 443
2112 if not url.hostname or not port:
2113 self.send_response(400)
2114 self.end_headers()
2115 return
2116
2117 if len(url.path) == 0:
2118 path = '/'
2119 else:
2120 path = url.path
2121 if len(url.query) > 0:
2122 path = '%s?%s' % (url.path, url.query)
2123
2124 sock = None
2125 try:
2126 sock = socket.create_connection((url.hostname, port))
2127 sock.send('%s %s %s\r\n' % (
2128 self.command, path, self.protocol_version))
2129 for header in self.headers.headers:
2130 header = header.strip()
2131 if (header.lower().startswith('connection') or
2132 header.lower().startswith('proxy')):
2133 continue
2134 sock.send('%s\r\n' % header)
2135 sock.send('\r\n')
2136 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002137 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002138 self.send_response(500)
2139 self.end_headers()
2140 finally:
2141 if sock is not None:
2142 sock.close()
2143
2144 def do_CONNECT(self):
2145 try:
2146 pos = self.path.rfind(':')
2147 host = self.path[:pos]
2148 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002149 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002150 self.send_response(400)
2151 self.end_headers()
2152
2153 try:
2154 sock = socket.create_connection((host, port))
2155 self.send_response(200, 'Connection established')
2156 self.end_headers()
2157 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002158 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00002159 self.send_response(500)
2160 self.end_headers()
2161 finally:
2162 sock.close()
2163
2164 def do_GET(self):
2165 self._do_common_method()
2166
2167 def do_HEAD(self):
2168 self._do_common_method()
2169
2170
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002171class FileMultiplexer:
2172 def __init__(self, fd1, fd2) :
2173 self.__fd1 = fd1
2174 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002175
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002176 def __del__(self) :
newt@chromium.org1fc32742012-10-20 00:28:35 +00002177 self.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002178
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002179 def write(self, text) :
2180 self.__fd1.write(text)
2181 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002182
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002183 def flush(self) :
2184 self.__fd1.flush()
2185 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002186
newt@chromium.org1fc32742012-10-20 00:28:35 +00002187 def close(self):
2188 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2189 self.__fd1.close()
2190 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2191 self.__fd2.close()
2192
2193
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002194def main(options, _args):
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002195 logfile = open('testserver.log', 'w')
2196 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2197 if options.log_to_console:
2198 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2199 else:
2200 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002201
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002202 port = options.port
2203 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002204
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002205 server_data = {}
2206 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002207
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002208 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002209
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002210 if options.server_type == SERVER_HTTP:
2211 if options.https:
2212 pem_cert_and_key = None
2213 if options.cert_and_key_file:
2214 if not os.path.isfile(options.cert_and_key_file):
2215 print ('specified server cert file not found: ' +
2216 options.cert_and_key_file + ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002217 return 1
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002218 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002219 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002220 # generate a new certificate and run an OCSP server for it.
2221 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2222 print ('OCSP server started on %s:%d...' %
2223 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002224
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002225 ocsp_der = None
2226 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002227
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002228 if options.ocsp == 'ok':
2229 ocsp_state = minica.OCSP_STATE_GOOD
2230 elif options.ocsp == 'revoked':
2231 ocsp_state = minica.OCSP_STATE_REVOKED
2232 elif options.ocsp == 'invalid':
2233 ocsp_state = minica.OCSP_STATE_INVALID
2234 elif options.ocsp == 'unauthorized':
2235 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2236 elif options.ocsp == 'unknown':
2237 ocsp_state = minica.OCSP_STATE_UNKNOWN
2238 else:
2239 print 'unknown OCSP status: ' + options.ocsp_status
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002240 return 1
mattm@chromium.org07e28412012-09-05 00:19:41 +00002241
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002242 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2243 subject = "127.0.0.1",
2244 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server.server_port)),
2245 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002246
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002247 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002248
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002249 for ca_cert in options.ssl_client_ca:
2250 if not os.path.isfile(ca_cert):
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002251 print ('specified trusted client CA file not found: ' + ca_cert +
2252 ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002253 return 1
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002254 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2255 options.ssl_client_auth, options.ssl_client_ca,
2256 options.ssl_bulk_cipher, options.record_resume,
2257 options.tls_intolerant)
2258 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002259 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002260 server = HTTPServer((host, port), TestPageHandler)
2261 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002262
newt@chromium.org1fc32742012-10-20 00:28:35 +00002263 server.data_dir = MakeDataDir(options)
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002264 server.file_root_url = options.file_root_url
2265 server_data['port'] = server.server_port
2266 server._device_management_handler = None
2267 server.policy_keys = options.policy_keys
2268 server.policy_user = options.policy_user
2269 server.gdata_auth_token = options.auth_token
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002270 elif options.server_type == SERVER_WEBSOCKET:
2271 # Launch pywebsocket via WebSocketServer.
2272 logger = logging.getLogger()
2273 logger.addHandler(logging.StreamHandler())
2274 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2275 # is required to work correctly. It should be fixed from pywebsocket side.
newt@chromium.org1fc32742012-10-20 00:28:35 +00002276 os.chdir(MakeDataDir(options))
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002277 websocket_options = WebSocketOptions(host, port, '.')
2278 if options.cert_and_key_file:
2279 websocket_options.use_tls = True
2280 websocket_options.private_key = options.cert_and_key_file
2281 websocket_options.certificate = options.cert_and_key_file
2282 if options.ssl_client_auth:
2283 websocket_options.tls_client_auth = True
2284 if len(options.ssl_client_ca) != 1:
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002285 print 'one trusted client CA file should be specified'
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002286 return 1
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002287 if not os.path.isfile(options.ssl_client_ca[0]):
2288 print ('specified trusted client CA file not found: ' +
2289 options.ssl_client_ca[0] + ' exiting...')
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002290 return 1
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +00002291 websocket_options.tls_client_ca = options.ssl_client_ca[0]
2292 server = WebSocketServer(websocket_options)
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002293 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2294 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002295 elif options.server_type == SERVER_SYNC:
2296 xmpp_port = options.xmpp_port
2297 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2298 print 'Sync HTTP server started on port %d...' % server.server_port
2299 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2300 server_data['port'] = server.server_port
2301 server_data['xmpp_port'] = server.xmpp_port
2302 elif options.server_type == SERVER_TCP_ECHO:
2303 # Used for generating the key (randomly) that encodes the "echo request"
2304 # message.
2305 random.seed()
2306 server = TCPEchoServer((host, port), TCPEchoHandler)
2307 print 'Echo TCP server started on port %d...' % server.server_port
2308 server_data['port'] = server.server_port
2309 elif options.server_type == SERVER_UDP_ECHO:
2310 # Used for generating the key (randomly) that encodes the "echo request"
2311 # message.
2312 random.seed()
2313 server = UDPEchoServer((host, port), UDPEchoHandler)
2314 print 'Echo UDP server started on port %d...' % server.server_port
2315 server_data['port'] = server.server_port
bashi@chromium.org33233532012-09-08 17:37:24 +00002316 elif options.server_type == SERVER_BASIC_AUTH_PROXY:
2317 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2318 print 'BasicAuthProxy server started on port %d...' % server.server_port
2319 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002320 # means FTP Server
2321 else:
newt@chromium.org1fc32742012-10-20 00:28:35 +00002322 my_data_dir = MakeDataDir(options)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002323
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002324 # Instantiate a dummy authorizer for managing 'virtual' users
2325 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002326
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002327 # Define a new user having full r/w permissions and a read-only
2328 # anonymous user
2329 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002330
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002331 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002332
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002333 # Instantiate FTP handler class
2334 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2335 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002336
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002337 # Define a customized banner (string returned when client connects)
2338 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2339 pyftpdlib.ftpserver.__ver__)
2340
2341 # Instantiate FTP server class and listen to address:port
2342 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2343 server_data['port'] = server.socket.getsockname()[1]
2344 print 'FTP server started on port %d...' % server_data['port']
2345
2346 # Notify the parent that we've started. (BaseServer subclasses
2347 # bind their sockets on construction.)
2348 if options.startup_pipe is not None:
2349 server_data_json = json.dumps(server_data)
2350 server_data_len = len(server_data_json)
2351 print 'sending server_data: %s (%d bytes)' % (
2352 server_data_json, server_data_len)
2353 if sys.platform == 'win32':
2354 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2355 else:
2356 fd = options.startup_pipe
2357 startup_pipe = os.fdopen(fd, "w")
2358 # First write the data length as an unsigned 4-byte value. This
2359 # is _not_ using network byte ordering since the other end of the
2360 # pipe is on the same machine.
2361 startup_pipe.write(struct.pack('=L', server_data_len))
2362 startup_pipe.write(server_data_json)
2363 startup_pipe.close()
2364
2365 if ocsp_server is not None:
2366 ocsp_server.serve_forever_on_thread()
2367
2368 try:
2369 server.serve_forever()
2370 except KeyboardInterrupt:
2371 print 'shutting down server'
2372 if ocsp_server is not None:
2373 ocsp_server.stop_serving()
2374 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002375
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002376 return 0
2377
initial.commit94958cf2008-07-26 22:42:52 +00002378if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002379 option_parser = optparse.OptionParser()
2380 option_parser.add_option("-f", '--ftp', action='store_const',
2381 const=SERVER_FTP, default=SERVER_HTTP,
2382 dest='server_type',
2383 help='start up an FTP server.')
2384 option_parser.add_option('', '--sync', action='store_const',
2385 const=SERVER_SYNC, default=SERVER_HTTP,
2386 dest='server_type',
2387 help='start up a sync server.')
2388 option_parser.add_option('', '--tcp-echo', action='store_const',
2389 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2390 dest='server_type',
2391 help='start up a tcp echo server.')
2392 option_parser.add_option('', '--udp-echo', action='store_const',
2393 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2394 dest='server_type',
2395 help='start up a udp echo server.')
bashi@chromium.org33233532012-09-08 17:37:24 +00002396 option_parser.add_option('', '--basic-auth-proxy', action='store_const',
2397 const=SERVER_BASIC_AUTH_PROXY, default=SERVER_HTTP,
2398 dest='server_type',
2399 help='start up a proxy server which requires basic '
2400 'authentication.')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002401 option_parser.add_option('', '--websocket', action='store_const',
2402 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2403 dest='server_type',
2404 help='start up a WebSocket server.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002405 option_parser.add_option('', '--log-to-console', action='store_const',
2406 const=True, default=False,
2407 dest='log_to_console',
2408 help='Enables or disables sys.stdout logging to '
2409 'the console.')
2410 option_parser.add_option('', '--port', default='0', type='int',
2411 help='Port used by the server. If unspecified, the '
2412 'server will listen on an ephemeral port.')
2413 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2414 help='Port used by the XMPP server. If unspecified, '
2415 'the XMPP server will listen on an ephemeral port.')
2416 option_parser.add_option('', '--data-dir', dest='data_dir',
2417 help='Directory from which to read the files.')
2418 option_parser.add_option('', '--https', action='store_true', dest='https',
2419 help='Specify that https should be used.')
2420 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2421 help='specify the path to the file containing the '
2422 'certificate and private key for the server in PEM '
2423 'format')
2424 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2425 help='The type of OCSP response generated for the '
2426 'automatically generated certificate. One of '
2427 '[ok,revoked,invalid]')
2428 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2429 default='0', type='int',
2430 help='If nonzero, certain TLS connections will be'
2431 ' aborted in order to test version fallback. 1'
2432 ' means all TLS versions will be aborted. 2 means'
2433 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2434 ' 1.2 or higher will be aborted.')
2435 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2436 const=True, default=False, action='store_const',
2437 help='Record resumption cache events rather than'
2438 ' resuming as normal. Allows the use of the'
2439 ' /ssl-session-cache request')
2440 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2441 help='Require SSL client auth on every connection.')
2442 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2443 help='Specify that the client certificate request '
2444 'should include the CA named in the subject of '
2445 'the DER-encoded certificate contained in the '
2446 'specified file. This option may appear multiple '
2447 'times, indicating multiple CA names should be '
2448 'sent in the request.')
2449 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2450 help='Specify the bulk encryption algorithm(s)'
2451 'that will be accepted by the SSL server. Valid '
2452 'values are "aes256", "aes128", "3des", "rc4". If '
2453 'omitted, all algorithms will be used. This '
2454 'option may appear multiple times, indicating '
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002455 'multiple algorithms should be enabled.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002456 option_parser.add_option('', '--file-root-url', default='/files/',
2457 help='Specify a root URL for files served.')
2458 option_parser.add_option('', '--startup-pipe', type='int',
2459 dest='startup_pipe',
2460 help='File handle of pipe to parent process')
2461 option_parser.add_option('', '--policy-key', action='append',
2462 dest='policy_keys',
2463 help='Specify a path to a PEM-encoded private key '
2464 'to use for policy signing. May be specified '
2465 'multiple times in order to load multipe keys into '
2466 'the server. If ther server has multiple keys, it '
2467 'will rotate through them in at each request a '
2468 'round-robin fashion. The server will generate a '
2469 'random key if none is specified on the command '
2470 'line.')
2471 option_parser.add_option('', '--policy-user', default='user@example.com',
2472 dest='policy_user',
2473 help='Specify the user name the server should '
2474 'report back to the client as the user owning the '
2475 'token used for making the policy request.')
2476 option_parser.add_option('', '--host', default='127.0.0.1',
2477 dest='host',
2478 help='Hostname or IP upon which the server will '
2479 'listen. Client connections will also only be '
2480 'allowed from this address.')
2481 option_parser.add_option('', '--auth-token', dest='auth_token',
2482 help='Specify the auth token which should be used'
2483 'in the authorization header for GData.')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002484 main_options, main_args = option_parser.parse_args()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002485
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00002486 sys.exit(main(main_options, main_args))