blob: ff684a8afaa888865c68ebabb0c4f0538bb62de9 [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
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000021import httplib
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000023import optparse
initial.commit94958cf2008-07-26 22:42:52 +000024import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000030import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import sys
32import threading
initial.commit94958cf2008-07-26 22:42:52 +000033import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000034import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000035import urlparse
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000036import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000039# Ignore deprecation warnings, they make our output more cluttered.
40warnings.filterwarnings("ignore", category=DeprecationWarning)
41
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000042import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000043import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000044import tlslite
45import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000046
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000047try:
48 import hashlib
49 _new_md5 = hashlib.md5
50except ImportError:
51 import md5
52 _new_md5 = md5.new
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000053
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000054try:
55 import json
56except ImportError:
57 import simplejson as json
58
59if sys.platform == 'win32':
60 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000061
maruel@chromium.org756cf982009-03-05 12:46:38 +000062SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000063SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000064SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000065SERVER_TCP_ECHO = 3
66SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000067SERVER_BASIC_AUTH_PROXY = 5
initial.commit94958cf2008-07-26 22:42:52 +000068
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000069# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000070debug_output = sys.stderr
71def debug(str):
72 debug_output.write(str + "\n")
73 debug_output.flush()
74
agl@chromium.orgf9e66792011-12-12 22:22:19 +000075class RecordingSSLSessionCache(object):
76 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
77 lookups and inserts in order to test session cache behaviours."""
78
79 def __init__(self):
80 self.log = []
81
82 def __getitem__(self, sessionID):
83 self.log.append(('lookup', sessionID))
84 raise KeyError()
85
86 def __setitem__(self, sessionID, session):
87 self.log.append(('insert', sessionID))
88
erikwright@chromium.org847ef282012-02-22 16:41:10 +000089
90class ClientRestrictingServerMixIn:
91 """Implements verify_request to limit connections to our configured IP
92 address."""
93
94 def verify_request(self, request, client_address):
95 return client_address[0] == self.server_address[0]
96
97
initial.commit94958cf2008-07-26 22:42:52 +000098class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +000099 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000100 to be exited cleanly (by setting its "stop" member to True)."""
101
102 def serve_forever(self):
103 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000104 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000105 while not self.stop:
106 self.handle_request()
107 self.socket.close()
108
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000109
110class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000111 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000112 verification."""
113
114 pass
115
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000116class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
117 """This is a specialization of HTTPServer that serves an
118 OCSP response"""
119
120 def serve_forever_on_thread(self):
121 self.thread = threading.Thread(target = self.serve_forever,
122 name = "OCSPServerThread")
123 self.thread.start()
124
125 def stop_serving(self):
126 self.shutdown()
127 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000128
129class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
130 ClientRestrictingServerMixIn,
131 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000132 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000133 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000134
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000135 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000136 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000137 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000138 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
139 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000140 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000141 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000142 self.tls_intolerant = tls_intolerant
143
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000144 for ca_file in ssl_client_cas:
145 s = open(ca_file).read()
146 x509 = tlslite.api.X509()
147 x509.parse(s)
148 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000149 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
150 if ssl_bulk_ciphers is not None:
151 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000152
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000153 if record_resume_info:
154 # If record_resume_info is true then we'll replace the session cache with
155 # an object that records the lookups and inserts that it sees.
156 self.session_cache = RecordingSSLSessionCache()
157 else:
158 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000159 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
160
161 def handshake(self, tlsConnection):
162 """Creates the SSL connection."""
163 try:
164 tlsConnection.handshakeServer(certChain=self.cert_chain,
165 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000166 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000167 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000168 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000169 reqCAs=self.ssl_client_cas,
170 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000171 tlsConnection.ignoreAbruptClose = True
172 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000173 except tlslite.api.TLSAbruptCloseError:
174 # Ignore abrupt close.
175 return True
initial.commit94958cf2008-07-26 22:42:52 +0000176 except tlslite.api.TLSError, error:
177 print "Handshake failure:", str(error)
178 return False
179
akalin@chromium.org154bb132010-11-12 02:20:27 +0000180
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000181class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000182 """An HTTP server that handles sync commands."""
183
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000184 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000185 # We import here to avoid pulling in chromiumsync's dependencies
186 # unless strictly necessary.
187 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000188 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000189 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000190 self._sync_handler = chromiumsync.TestServer()
191 self._xmpp_socket_map = {}
192 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000193 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000194 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000195 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000196
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000197 def GetXmppServer(self):
198 return self._xmpp_server
199
akalin@chromium.org154bb132010-11-12 02:20:27 +0000200 def HandleCommand(self, query, raw_request):
201 return self._sync_handler.HandleCommand(query, raw_request)
202
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000203 def HandleRequestNoBlock(self):
204 """Handles a single request.
205
206 Copied from SocketServer._handle_request_noblock().
207 """
208 try:
209 request, client_address = self.get_request()
210 except socket.error:
211 return
212 if self.verify_request(request, client_address):
213 try:
214 self.process_request(request, client_address)
215 except:
216 self.handle_error(request, client_address)
217 self.close_request(request)
218
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000219 def SetAuthenticated(self, auth_valid):
220 self.authenticated = auth_valid
221
222 def GetAuthenticated(self):
223 return self.authenticated
224
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000225 def serve_forever(self):
226 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
227 """
228
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000229 def HandleXmppSocket(fd, socket_map, handler):
230 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000231
232 Adapted from asyncore.read() et al.
233 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000234 xmpp_connection = socket_map.get(fd)
235 # This could happen if a previous handler call caused fd to get
236 # removed from socket_map.
237 if xmpp_connection is None:
238 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000239 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000240 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000241 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
242 raise
243 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000244 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000245
246 while True:
247 read_fds = [ self.fileno() ]
248 write_fds = []
249 exceptional_fds = []
250
251 for fd, xmpp_connection in self._xmpp_socket_map.items():
252 is_r = xmpp_connection.readable()
253 is_w = xmpp_connection.writable()
254 if is_r:
255 read_fds.append(fd)
256 if is_w:
257 write_fds.append(fd)
258 if is_r or is_w:
259 exceptional_fds.append(fd)
260
261 try:
262 read_fds, write_fds, exceptional_fds = (
263 select.select(read_fds, write_fds, exceptional_fds))
264 except select.error, err:
265 if err.args[0] != errno.EINTR:
266 raise
267 else:
268 continue
269
270 for fd in read_fds:
271 if fd == self.fileno():
272 self.HandleRequestNoBlock()
273 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000274 HandleXmppSocket(fd, self._xmpp_socket_map,
275 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000276
277 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000278 HandleXmppSocket(fd, self._xmpp_socket_map,
279 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000280
281 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000282 HandleXmppSocket(fd, self._xmpp_socket_map,
283 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000284
akalin@chromium.org154bb132010-11-12 02:20:27 +0000285
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000286class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
287 """This is a specialization of FTPServer that adds client verification."""
288
289 pass
290
291
292class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000293 """A TCP echo server that echoes back what it has received."""
294
295 def server_bind(self):
296 """Override server_bind to store the server name."""
297 SocketServer.TCPServer.server_bind(self)
298 host, port = self.socket.getsockname()[:2]
299 self.server_name = socket.getfqdn(host)
300 self.server_port = port
301
302 def serve_forever(self):
303 self.stop = False
304 self.nonce_time = None
305 while not self.stop:
306 self.handle_request()
307 self.socket.close()
308
309
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000310class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000311 """A UDP echo server that echoes back what it has received."""
312
313 def server_bind(self):
314 """Override server_bind to store the server name."""
315 SocketServer.UDPServer.server_bind(self)
316 host, port = self.socket.getsockname()[:2]
317 self.server_name = socket.getfqdn(host)
318 self.server_port = port
319
320 def serve_forever(self):
321 self.stop = False
322 self.nonce_time = None
323 while not self.stop:
324 self.handle_request()
325 self.socket.close()
326
327
akalin@chromium.org154bb132010-11-12 02:20:27 +0000328class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
329
330 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000331 connect_handlers, get_handlers, head_handlers, post_handlers,
332 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000333 self._connect_handlers = connect_handlers
334 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000335 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000336 self._post_handlers = post_handlers
337 self._put_handlers = put_handlers
338 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
339 self, request, client_address, socket_server)
340
341 def log_request(self, *args, **kwargs):
342 # Disable request logging to declutter test log output.
343 pass
344
345 def _ShouldHandleRequest(self, handler_name):
346 """Determines if the path can be handled by the handler.
347
348 We consider a handler valid if the path begins with the
349 handler name. It can optionally be followed by "?*", "/*".
350 """
351
352 pattern = re.compile('%s($|\?|/).*' % handler_name)
353 return pattern.match(self.path)
354
355 def do_CONNECT(self):
356 for handler in self._connect_handlers:
357 if handler():
358 return
359
360 def do_GET(self):
361 for handler in self._get_handlers:
362 if handler():
363 return
364
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000365 def do_HEAD(self):
366 for handler in self._head_handlers:
367 if handler():
368 return
369
akalin@chromium.org154bb132010-11-12 02:20:27 +0000370 def do_POST(self):
371 for handler in self._post_handlers:
372 if handler():
373 return
374
375 def do_PUT(self):
376 for handler in self._put_handlers:
377 if handler():
378 return
379
380
381class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000382
383 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000384 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000385 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000386 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000387 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000388 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000389 self.NoCacheMaxAgeTimeHandler,
390 self.NoCacheTimeHandler,
391 self.CacheTimeHandler,
392 self.CacheExpiresHandler,
393 self.CacheProxyRevalidateHandler,
394 self.CachePrivateHandler,
395 self.CachePublicHandler,
396 self.CacheSMaxAgeHandler,
397 self.CacheMustRevalidateHandler,
398 self.CacheMustRevalidateMaxAgeHandler,
399 self.CacheNoStoreHandler,
400 self.CacheNoStoreMaxAgeHandler,
401 self.CacheNoTransformHandler,
402 self.DownloadHandler,
403 self.DownloadFinishHandler,
404 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000405 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000406 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000407 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000408 self.GDataAuthHandler,
409 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000410 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000411 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000412 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000413 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000414 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000415 self.AuthBasicHandler,
416 self.AuthDigestHandler,
417 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000418 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000419 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000420 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000421 self.ServerRedirectHandler,
422 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000423 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000424 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000425 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000426 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000427 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000428 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000429 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000430 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000431 self.DeviceManagementHandler,
432 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000433 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000434 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000435 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000436 head_handlers = [
437 self.FileHandler,
438 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000439
maruel@google.come250a9b2009-03-10 17:39:46 +0000440 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000441 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000442 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 'gif': 'image/gif',
444 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000445 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000446 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000447 'pdf' : 'application/pdf',
448 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000449 }
initial.commit94958cf2008-07-26 22:42:52 +0000450 self._default_mime_type = 'text/html'
451
akalin@chromium.org154bb132010-11-12 02:20:27 +0000452 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000453 connect_handlers, get_handlers, head_handlers,
454 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000455
initial.commit94958cf2008-07-26 22:42:52 +0000456 def GetMIMETypeFromName(self, file_name):
457 """Returns the mime type for the specified file_name. So far it only looks
458 at the file extension."""
459
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000460 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000461 if len(extension) == 0:
462 # no extension.
463 return self._default_mime_type
464
ericroman@google.comc17ca532009-05-07 03:51:05 +0000465 # extension starts with a dot, so we need to remove it
466 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000467
initial.commit94958cf2008-07-26 22:42:52 +0000468 def NoCacheMaxAgeTimeHandler(self):
469 """This request handler yields a page with the title set to the current
470 system time, and no caching requested."""
471
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000472 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000473 return False
474
475 self.send_response(200)
476 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000477 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000478 self.end_headers()
479
maruel@google.come250a9b2009-03-10 17:39:46 +0000480 self.wfile.write('<html><head><title>%s</title></head></html>' %
481 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000482
483 return True
484
485 def NoCacheTimeHandler(self):
486 """This request handler yields a page with the title set to the current
487 system time, and no caching requested."""
488
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000489 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000490 return False
491
492 self.send_response(200)
493 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000494 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000495 self.end_headers()
496
maruel@google.come250a9b2009-03-10 17:39:46 +0000497 self.wfile.write('<html><head><title>%s</title></head></html>' %
498 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000499
500 return True
501
502 def CacheTimeHandler(self):
503 """This request handler yields a page with the title set to the current
504 system time, and allows caching for one minute."""
505
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000506 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000507 return False
508
509 self.send_response(200)
510 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000511 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000512 self.end_headers()
513
maruel@google.come250a9b2009-03-10 17:39:46 +0000514 self.wfile.write('<html><head><title>%s</title></head></html>' %
515 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000516
517 return True
518
519 def CacheExpiresHandler(self):
520 """This request handler yields a page with the title set to the current
521 system time, and set the page to expire on 1 Jan 2099."""
522
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000523 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000524 return False
525
526 self.send_response(200)
527 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000528 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000529 self.end_headers()
530
maruel@google.come250a9b2009-03-10 17:39:46 +0000531 self.wfile.write('<html><head><title>%s</title></head></html>' %
532 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000533
534 return True
535
536 def CacheProxyRevalidateHandler(self):
537 """This request handler yields a page with the title set to the current
538 system time, and allows caching for 60 seconds"""
539
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000540 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000541 return False
542
543 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000544 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000545 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
546 self.end_headers()
547
maruel@google.come250a9b2009-03-10 17:39:46 +0000548 self.wfile.write('<html><head><title>%s</title></head></html>' %
549 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000550
551 return True
552
553 def CachePrivateHandler(self):
554 """This request handler yields a page with the title set to the current
555 system time, and allows caching for 5 seconds."""
556
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000557 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000558 return False
559
560 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000561 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000562 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000563 self.end_headers()
564
maruel@google.come250a9b2009-03-10 17:39:46 +0000565 self.wfile.write('<html><head><title>%s</title></head></html>' %
566 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000567
568 return True
569
570 def CachePublicHandler(self):
571 """This request handler yields a page with the title set to the current
572 system time, and allows caching for 5 seconds."""
573
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000574 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000575 return False
576
577 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000578 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000579 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000580 self.end_headers()
581
maruel@google.come250a9b2009-03-10 17:39:46 +0000582 self.wfile.write('<html><head><title>%s</title></head></html>' %
583 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000584
585 return True
586
587 def CacheSMaxAgeHandler(self):
588 """This request handler yields a page with the title set to the current
589 system time, and does not allow for caching."""
590
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000591 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000592 return False
593
594 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000595 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000596 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
597 self.end_headers()
598
maruel@google.come250a9b2009-03-10 17:39:46 +0000599 self.wfile.write('<html><head><title>%s</title></head></html>' %
600 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000601
602 return True
603
604 def CacheMustRevalidateHandler(self):
605 """This request handler yields a page with the title set to the current
606 system time, and does not allow caching."""
607
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000608 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000609 return False
610
611 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000612 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000613 self.send_header('Cache-Control', 'must-revalidate')
614 self.end_headers()
615
maruel@google.come250a9b2009-03-10 17:39:46 +0000616 self.wfile.write('<html><head><title>%s</title></head></html>' %
617 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000618
619 return True
620
621 def CacheMustRevalidateMaxAgeHandler(self):
622 """This request handler yields a page with the title set to the current
623 system time, and does not allow caching event though max-age of 60
624 seconds is specified."""
625
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000626 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000627 return False
628
629 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000630 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000631 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
632 self.end_headers()
633
maruel@google.come250a9b2009-03-10 17:39:46 +0000634 self.wfile.write('<html><head><title>%s</title></head></html>' %
635 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000636
637 return True
638
initial.commit94958cf2008-07-26 22:42:52 +0000639 def CacheNoStoreHandler(self):
640 """This request handler yields a page with the title set to the current
641 system time, and does not allow the page to be stored."""
642
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000643 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000644 return False
645
646 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000647 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000648 self.send_header('Cache-Control', 'no-store')
649 self.end_headers()
650
maruel@google.come250a9b2009-03-10 17:39:46 +0000651 self.wfile.write('<html><head><title>%s</title></head></html>' %
652 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000653
654 return True
655
656 def CacheNoStoreMaxAgeHandler(self):
657 """This request handler yields a page with the title set to the current
658 system time, and does not allow the page to be stored even though max-age
659 of 60 seconds is specified."""
660
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000661 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000662 return False
663
664 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000665 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000666 self.send_header('Cache-Control', 'max-age=60, no-store')
667 self.end_headers()
668
maruel@google.come250a9b2009-03-10 17:39:46 +0000669 self.wfile.write('<html><head><title>%s</title></head></html>' %
670 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000671
672 return True
673
674
675 def CacheNoTransformHandler(self):
676 """This request handler yields a page with the title set to the current
677 system time, and does not allow the content to transformed during
678 user-agent caching"""
679
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000680 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000681 return False
682
683 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000684 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000685 self.send_header('Cache-Control', 'no-transform')
686 self.end_headers()
687
maruel@google.come250a9b2009-03-10 17:39:46 +0000688 self.wfile.write('<html><head><title>%s</title></head></html>' %
689 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000690
691 return True
692
693 def EchoHeader(self):
694 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000695 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000696
ananta@chromium.org56812d02011-04-07 17:52:05 +0000697 """This function echoes back the value of a specific request header"""
698 """while allowing caching for 16 hours."""
699 def EchoHeaderCache(self):
700 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000701
702 def EchoHeaderHelper(self, echo_header):
703 """This function echoes back the value of the request header passed in."""
704 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000705 return False
706
707 query_char = self.path.find('?')
708 if query_char != -1:
709 header_name = self.path[query_char+1:]
710
711 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000712 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000713 if echo_header == '/echoheadercache':
714 self.send_header('Cache-control', 'max-age=60000')
715 else:
716 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000717 # insert a vary header to properly indicate that the cachability of this
718 # request is subject to value of the request header being echoed.
719 if len(header_name) > 0:
720 self.send_header('Vary', header_name)
721 self.end_headers()
722
723 if len(header_name) > 0:
724 self.wfile.write(self.headers.getheader(header_name))
725
726 return True
727
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000728 def ReadRequestBody(self):
729 """This function reads the body of the current HTTP request, handling
730 both plain and chunked transfer encoded requests."""
731
732 if self.headers.getheader('transfer-encoding') != 'chunked':
733 length = int(self.headers.getheader('content-length'))
734 return self.rfile.read(length)
735
736 # Read the request body as chunks.
737 body = ""
738 while True:
739 line = self.rfile.readline()
740 length = int(line, 16)
741 if length == 0:
742 self.rfile.readline()
743 break
744 body += self.rfile.read(length)
745 self.rfile.read(2)
746 return body
747
initial.commit94958cf2008-07-26 22:42:52 +0000748 def EchoHandler(self):
749 """This handler just echoes back the payload of the request, for testing
750 form submission."""
751
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000752 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000753 return False
754
755 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000756 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000757 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000758 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000759 return True
760
761 def EchoTitleHandler(self):
762 """This handler is like Echo, but sets the page title to the request."""
763
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000764 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000765 return False
766
767 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000768 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000769 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000770 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000771 self.wfile.write('<html><head><title>')
772 self.wfile.write(request)
773 self.wfile.write('</title></head></html>')
774 return True
775
776 def EchoAllHandler(self):
777 """This handler yields a (more) human-readable page listing information
778 about the request header & contents."""
779
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000780 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000781 return False
782
783 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000784 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000785 self.end_headers()
786 self.wfile.write('<html><head><style>'
787 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
788 '</style></head><body>'
789 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000790 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000791 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000792
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000793 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000794 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000795 params = cgi.parse_qs(qs, keep_blank_values=1)
796
797 for param in params:
798 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000799
800 self.wfile.write('</pre>')
801
802 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
803
804 self.wfile.write('</body></html>')
805 return True
806
807 def DownloadHandler(self):
808 """This handler sends a downloadable file with or without reporting
809 the size (6K)."""
810
811 if self.path.startswith("/download-unknown-size"):
812 send_length = False
813 elif self.path.startswith("/download-known-size"):
814 send_length = True
815 else:
816 return False
817
818 #
819 # The test which uses this functionality is attempting to send
820 # small chunks of data to the client. Use a fairly large buffer
821 # so that we'll fill chrome's IO buffer enough to force it to
822 # actually write the data.
823 # See also the comments in the client-side of this test in
824 # download_uitest.cc
825 #
826 size_chunk1 = 35*1024
827 size_chunk2 = 10*1024
828
829 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000830 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000831 self.send_header('Cache-Control', 'max-age=0')
832 if send_length:
833 self.send_header('Content-Length', size_chunk1 + size_chunk2)
834 self.end_headers()
835
836 # First chunk of data:
837 self.wfile.write("*" * size_chunk1)
838 self.wfile.flush()
839
840 # handle requests until one of them clears this flag.
841 self.server.waitForDownload = True
842 while self.server.waitForDownload:
843 self.server.handle_request()
844
845 # Second chunk of data:
846 self.wfile.write("*" * size_chunk2)
847 return True
848
849 def DownloadFinishHandler(self):
850 """This handler just tells the server to finish the current download."""
851
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000852 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000853 return False
854
855 self.server.waitForDownload = False
856 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000857 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000858 self.send_header('Cache-Control', 'max-age=0')
859 self.end_headers()
860 return True
861
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000862 def _ReplaceFileData(self, data, query_parameters):
863 """Replaces matching substrings in a file.
864
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000865 If the 'replace_text' URL query parameter is present, it is expected to be
866 of the form old_text:new_text, which indicates that any old_text strings in
867 the file are replaced with new_text. Multiple 'replace_text' parameters may
868 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000869
870 If the parameters are not present, |data| is returned.
871 """
872 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000873 replace_text_values = query_dict.get('replace_text', [])
874 for replace_text_value in replace_text_values:
875 replace_text_args = replace_text_value.split(':')
876 if len(replace_text_args) != 2:
877 raise ValueError(
878 'replace_text must be of form old_text:new_text. Actual value: %s' %
879 replace_text_value)
880 old_text_b64, new_text_b64 = replace_text_args
881 old_text = base64.urlsafe_b64decode(old_text_b64)
882 new_text = base64.urlsafe_b64decode(new_text_b64)
883 data = data.replace(old_text, new_text)
884 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000885
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000886 def ZipFileHandler(self):
887 """This handler sends the contents of the requested file in compressed form.
888 Can pass in a parameter that specifies that the content length be
889 C - the compressed size (OK),
890 U - the uncompressed size (Non-standard, but handled),
891 S - less than compressed (OK because we keep going),
892 M - larger than compressed but less than uncompressed (an error),
893 L - larger than uncompressed (an error)
894 Example: compressedfiles/Picture_1.doc?C
895 """
896
897 prefix = "/compressedfiles/"
898 if not self.path.startswith(prefix):
899 return False
900
901 # Consume a request body if present.
902 if self.command == 'POST' or self.command == 'PUT' :
903 self.ReadRequestBody()
904
905 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
906
907 if not query in ('C', 'U', 'S', 'M', 'L'):
908 return False
909
910 sub_path = url_path[len(prefix):]
911 entries = sub_path.split('/')
912 file_path = os.path.join(self.server.data_dir, *entries)
913 if os.path.isdir(file_path):
914 file_path = os.path.join(file_path, 'index.html')
915
916 if not os.path.isfile(file_path):
917 print "File not found " + sub_path + " full path:" + file_path
918 self.send_error(404)
919 return True
920
921 f = open(file_path, "rb")
922 data = f.read()
923 uncompressed_len = len(data)
924 f.close()
925
926 # Compress the data.
927 data = zlib.compress(data)
928 compressed_len = len(data)
929
930 content_length = compressed_len
931 if query == 'U':
932 content_length = uncompressed_len
933 elif query == 'S':
934 content_length = compressed_len / 2
935 elif query == 'M':
936 content_length = (compressed_len + uncompressed_len) / 2
937 elif query == 'L':
938 content_length = compressed_len + uncompressed_len
939
940 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000941 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000942 self.send_header('Content-encoding', 'deflate')
943 self.send_header('Connection', 'close')
944 self.send_header('Content-Length', content_length)
945 self.send_header('ETag', '\'' + file_path + '\'')
946 self.end_headers()
947
948 self.wfile.write(data)
949
950 return True
951
initial.commit94958cf2008-07-26 22:42:52 +0000952 def FileHandler(self):
953 """This handler sends the contents of the requested file. Wow, it's like
954 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000955 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000956 if not self.path.startswith(prefix):
957 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000958 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000959
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000960 def PostOnlyFileHandler(self):
961 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000962 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000963 if not self.path.startswith(prefix):
964 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000965 return self._FileHandlerHelper(prefix)
966
967 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000968 request_body = ''
969 if self.command == 'POST' or self.command == 'PUT':
970 # Consume a request body if present.
971 request_body = self.ReadRequestBody()
972
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000973 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000974 query_dict = cgi.parse_qs(query)
975
976 expected_body = query_dict.get('expected_body', [])
977 if expected_body and request_body not in expected_body:
978 self.send_response(404)
979 self.end_headers()
980 self.wfile.write('')
981 return True
982
983 expected_headers = query_dict.get('expected_headers', [])
984 for expected_header in expected_headers:
985 header_name, expected_value = expected_header.split(':')
986 if self.headers.getheader(header_name) != expected_value:
987 self.send_response(404)
988 self.end_headers()
989 self.wfile.write('')
990 return True
991
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000992 sub_path = url_path[len(prefix):]
993 entries = sub_path.split('/')
994 file_path = os.path.join(self.server.data_dir, *entries)
995 if os.path.isdir(file_path):
996 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000997
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000998 if not os.path.isfile(file_path):
999 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001000 self.send_error(404)
1001 return True
1002
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001003 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001004 data = f.read()
1005 f.close()
1006
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001007 data = self._ReplaceFileData(data, query)
1008
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001009 old_protocol_version = self.protocol_version
1010
initial.commit94958cf2008-07-26 22:42:52 +00001011 # If file.mock-http-headers exists, it contains the headers we
1012 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001013 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001014 if os.path.isfile(headers_path):
1015 f = open(headers_path, "r")
1016
1017 # "HTTP/1.1 200 OK"
1018 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001019 http_major, http_minor, status_code = re.findall(
1020 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1021 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001022 self.send_response(int(status_code))
1023
1024 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001025 header_values = re.findall('(\S+):\s*(.*)', line)
1026 if len(header_values) > 0:
1027 # "name: value"
1028 name, value = header_values[0]
1029 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001030 f.close()
1031 else:
1032 # Could be more generic once we support mime-type sniffing, but for
1033 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001034
1035 range = self.headers.get('Range')
1036 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001037 # Note this doesn't handle all valid byte range values (i.e. left
1038 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001039 range = range[6:].split('-')
1040 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001041 if range[1]:
1042 end = int(range[1])
1043 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001044 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001045
1046 self.send_response(206)
1047 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1048 str(len(data))
1049 self.send_header('Content-Range', content_range)
1050 data = data[start: end + 1]
1051 else:
1052 self.send_response(200)
1053
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001054 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001055 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001056 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001057 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001058 self.end_headers()
1059
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001060 if (self.command != 'HEAD'):
1061 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001062
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001063 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001064 return True
1065
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001066 def SetCookieHandler(self):
1067 """This handler just sets a cookie, for testing cookie handling."""
1068
1069 if not self._ShouldHandleRequest("/set-cookie"):
1070 return False
1071
1072 query_char = self.path.find('?')
1073 if query_char != -1:
1074 cookie_values = self.path[query_char + 1:].split('&')
1075 else:
1076 cookie_values = ("",)
1077 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001078 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001079 for cookie_value in cookie_values:
1080 self.send_header('Set-Cookie', '%s' % cookie_value)
1081 self.end_headers()
1082 for cookie_value in cookie_values:
1083 self.wfile.write('%s' % cookie_value)
1084 return True
1085
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001086 def SetManyCookiesHandler(self):
1087 """This handler just sets a given number of cookies, for testing handling
1088 of large numbers of cookies."""
1089
1090 if not self._ShouldHandleRequest("/set-many-cookies"):
1091 return False
1092
1093 query_char = self.path.find('?')
1094 if query_char != -1:
1095 num_cookies = int(self.path[query_char + 1:])
1096 else:
1097 num_cookies = 0
1098 self.send_response(200)
1099 self.send_header('', 'text/html')
1100 for i in range(0, num_cookies):
1101 self.send_header('Set-Cookie', 'a=')
1102 self.end_headers()
1103 self.wfile.write('%d cookies were sent' % num_cookies)
1104 return True
1105
mattm@chromium.org983fc462012-06-30 00:52:08 +00001106 def ExpectAndSetCookieHandler(self):
1107 """Expects some cookies to be sent, and if they are, sets more cookies.
1108
1109 The expect parameter specifies a required cookie. May be specified multiple
1110 times.
1111 The set parameter specifies a cookie to set if all required cookies are
1112 preset. May be specified multiple times.
1113 The data parameter specifies the response body data to be returned."""
1114
1115 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1116 return False
1117
1118 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1119 query_dict = cgi.parse_qs(query)
1120 cookies = set()
1121 if 'Cookie' in self.headers:
1122 cookie_header = self.headers.getheader('Cookie')
1123 cookies.update([s.strip() for s in cookie_header.split(';')])
1124 got_all_expected_cookies = True
1125 for expected_cookie in query_dict.get('expect', []):
1126 if expected_cookie not in cookies:
1127 got_all_expected_cookies = False
1128 self.send_response(200)
1129 self.send_header('Content-Type', 'text/html')
1130 if got_all_expected_cookies:
1131 for cookie_value in query_dict.get('set', []):
1132 self.send_header('Set-Cookie', '%s' % cookie_value)
1133 self.end_headers()
1134 for data_value in query_dict.get('data', []):
1135 self.wfile.write(data_value)
1136 return True
1137
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001138 def SetHeaderHandler(self):
1139 """This handler sets a response header. Parameters are in the
1140 key%3A%20value&key2%3A%20value2 format."""
1141
1142 if not self._ShouldHandleRequest("/set-header"):
1143 return False
1144
1145 query_char = self.path.find('?')
1146 if query_char != -1:
1147 headers_values = self.path[query_char + 1:].split('&')
1148 else:
1149 headers_values = ("",)
1150 self.send_response(200)
1151 self.send_header('Content-Type', 'text/html')
1152 for header_value in headers_values:
1153 header_value = urllib.unquote(header_value)
1154 (key, value) = header_value.split(': ', 1)
1155 self.send_header(key, value)
1156 self.end_headers()
1157 for header_value in headers_values:
1158 self.wfile.write('%s' % header_value)
1159 return True
1160
initial.commit94958cf2008-07-26 22:42:52 +00001161 def AuthBasicHandler(self):
1162 """This handler tests 'Basic' authentication. It just sends a page with
1163 title 'user/pass' if you succeed."""
1164
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001165 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001166 return False
1167
1168 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001169 expected_password = 'secret'
1170 realm = 'testrealm'
1171 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001172
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001173 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1174 query_params = cgi.parse_qs(query, True)
1175 if 'set-cookie-if-challenged' in query_params:
1176 set_cookie_if_challenged = True
1177 if 'password' in query_params:
1178 expected_password = query_params['password'][0]
1179 if 'realm' in query_params:
1180 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001181
initial.commit94958cf2008-07-26 22:42:52 +00001182 auth = self.headers.getheader('authorization')
1183 try:
1184 if not auth:
1185 raise Exception('no auth')
1186 b64str = re.findall(r'Basic (\S+)', auth)[0]
1187 userpass = base64.b64decode(b64str)
1188 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001189 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001190 raise Exception('wrong password')
1191 except Exception, e:
1192 # Authentication failed.
1193 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001194 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001195 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001196 if set_cookie_if_challenged:
1197 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001198 self.end_headers()
1199 self.wfile.write('<html><head>')
1200 self.wfile.write('<title>Denied: %s</title>' % e)
1201 self.wfile.write('</head><body>')
1202 self.wfile.write('auth=%s<p>' % auth)
1203 self.wfile.write('b64str=%s<p>' % b64str)
1204 self.wfile.write('username: %s<p>' % username)
1205 self.wfile.write('userpass: %s<p>' % userpass)
1206 self.wfile.write('password: %s<p>' % password)
1207 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1208 self.wfile.write('</body></html>')
1209 return True
1210
1211 # Authentication successful. (Return a cachable response to allow for
1212 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001213 old_protocol_version = self.protocol_version
1214 self.protocol_version = "HTTP/1.1"
1215
initial.commit94958cf2008-07-26 22:42:52 +00001216 if_none_match = self.headers.getheader('if-none-match')
1217 if if_none_match == "abc":
1218 self.send_response(304)
1219 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001220 elif url_path.endswith(".gif"):
1221 # Using chrome/test/data/google/logo.gif as the test image
1222 test_image_path = ['google', 'logo.gif']
1223 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1224 if not os.path.isfile(gif_path):
1225 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001226 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001227 return True
1228
1229 f = open(gif_path, "rb")
1230 data = f.read()
1231 f.close()
1232
1233 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001234 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001235 self.send_header('Cache-control', 'max-age=60000')
1236 self.send_header('Etag', 'abc')
1237 self.end_headers()
1238 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001239 else:
1240 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001241 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001242 self.send_header('Cache-control', 'max-age=60000')
1243 self.send_header('Etag', 'abc')
1244 self.end_headers()
1245 self.wfile.write('<html><head>')
1246 self.wfile.write('<title>%s/%s</title>' % (username, password))
1247 self.wfile.write('</head><body>')
1248 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001249 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001250 self.wfile.write('</body></html>')
1251
rvargas@google.com54453b72011-05-19 01:11:11 +00001252 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001253 return True
1254
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001255 def GDataAuthHandler(self):
1256 """This handler verifies the Authentication header for GData requests."""
1257 if not self.server.gdata_auth_token:
1258 # --auth-token is not specified, not the test case for GData.
1259 return False
1260
1261 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1262 return False
1263
1264 if 'GData-Version' not in self.headers:
1265 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1266 return True
1267
1268 if 'Authorization' not in self.headers:
1269 self.send_error(httplib.UNAUTHORIZED)
1270 return True
1271
1272 field_prefix = 'Bearer '
1273 authorization = self.headers['Authorization']
1274 if not authorization.startswith(field_prefix):
1275 self.send_error(httplib.UNAUTHORIZED)
1276 return True
1277
1278 code = authorization[len(field_prefix):]
1279 if code != self.server.gdata_auth_token:
1280 self.send_error(httplib.UNAUTHORIZED)
1281 return True
1282
1283 return False
1284
1285 def GDataDocumentsFeedQueryHandler(self):
1286 """This handler verifies if required parameters are properly
1287 specified for the GData DocumentsFeed request."""
1288 if not self.server.gdata_auth_token:
1289 # --auth-token is not specified, not the test case for GData.
1290 return False
1291
1292 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1293 return False
1294
1295 (path, question, query_params) = self.path.partition('?')
1296 self.query_params = urlparse.parse_qs(query_params)
1297
1298 if 'v' not in self.query_params:
1299 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1300 return True
1301 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1302 # currently our GData client only uses JSON format.
1303 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1304 return True
1305
1306 return False
1307
tonyg@chromium.org75054202010-03-31 22:06:10 +00001308 def GetNonce(self, force_reset=False):
1309 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001310
tonyg@chromium.org75054202010-03-31 22:06:10 +00001311 This is a fake implementation. A real implementation would only use a given
1312 nonce a single time (hence the name n-once). However, for the purposes of
1313 unittesting, we don't care about the security of the nonce.
1314
1315 Args:
1316 force_reset: Iff set, the nonce will be changed. Useful for testing the
1317 "stale" response.
1318 """
1319 if force_reset or not self.server.nonce_time:
1320 self.server.nonce_time = time.time()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001321 return _new_md5('privatekey%s%d' %
1322 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001323
1324 def AuthDigestHandler(self):
1325 """This handler tests 'Digest' authentication.
1326
1327 It just sends a page with title 'user/pass' if you succeed.
1328
1329 A stale response is sent iff "stale" is present in the request path.
1330 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001331 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001332 return False
1333
tonyg@chromium.org75054202010-03-31 22:06:10 +00001334 stale = 'stale' in self.path
1335 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001336 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001337 password = 'secret'
1338 realm = 'testrealm'
1339
1340 auth = self.headers.getheader('authorization')
1341 pairs = {}
1342 try:
1343 if not auth:
1344 raise Exception('no auth')
1345 if not auth.startswith('Digest'):
1346 raise Exception('not digest')
1347 # Pull out all the name="value" pairs as a dictionary.
1348 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1349
1350 # Make sure it's all valid.
1351 if pairs['nonce'] != nonce:
1352 raise Exception('wrong nonce')
1353 if pairs['opaque'] != opaque:
1354 raise Exception('wrong opaque')
1355
1356 # Check the 'response' value and make sure it matches our magic hash.
1357 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001358 hash_a1 = _new_md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001359 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001360 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001361 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001362 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001363 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1364 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001365 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001366
1367 if pairs['response'] != response:
1368 raise Exception('wrong password')
1369 except Exception, e:
1370 # Authentication failed.
1371 self.send_response(401)
1372 hdr = ('Digest '
1373 'realm="%s", '
1374 'domain="/", '
1375 'qop="auth", '
1376 'algorithm=MD5, '
1377 'nonce="%s", '
1378 'opaque="%s"') % (realm, nonce, opaque)
1379 if stale:
1380 hdr += ', stale="TRUE"'
1381 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001382 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001383 self.end_headers()
1384 self.wfile.write('<html><head>')
1385 self.wfile.write('<title>Denied: %s</title>' % e)
1386 self.wfile.write('</head><body>')
1387 self.wfile.write('auth=%s<p>' % auth)
1388 self.wfile.write('pairs=%s<p>' % pairs)
1389 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1390 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1391 self.wfile.write('</body></html>')
1392 return True
1393
1394 # Authentication successful.
1395 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001396 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001397 self.end_headers()
1398 self.wfile.write('<html><head>')
1399 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1400 self.wfile.write('</head><body>')
1401 self.wfile.write('auth=%s<p>' % auth)
1402 self.wfile.write('pairs=%s<p>' % pairs)
1403 self.wfile.write('</body></html>')
1404
1405 return True
1406
1407 def SlowServerHandler(self):
1408 """Wait for the user suggested time before responding. The syntax is
1409 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001410 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001411 return False
1412 query_char = self.path.find('?')
1413 wait_sec = 1.0
1414 if query_char >= 0:
1415 try:
1416 wait_sec = int(self.path[query_char + 1:])
1417 except ValueError:
1418 pass
1419 time.sleep(wait_sec)
1420 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001421 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001422 self.end_headers()
1423 self.wfile.write("waited %d seconds" % wait_sec)
1424 return True
1425
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001426 def ChunkedServerHandler(self):
1427 """Send chunked response. Allows to specify chunks parameters:
1428 - waitBeforeHeaders - ms to wait before sending headers
1429 - waitBetweenChunks - ms to wait between chunks
1430 - chunkSize - size of each chunk in bytes
1431 - chunksNumber - number of chunks
1432 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1433 waits one second, then sends headers and five chunks five bytes each."""
1434 if not self._ShouldHandleRequest("/chunked"):
1435 return False
1436 query_char = self.path.find('?')
1437 chunkedSettings = {'waitBeforeHeaders' : 0,
1438 'waitBetweenChunks' : 0,
1439 'chunkSize' : 5,
1440 'chunksNumber' : 5}
1441 if query_char >= 0:
1442 params = self.path[query_char + 1:].split('&')
1443 for param in params:
1444 keyValue = param.split('=')
1445 if len(keyValue) == 2:
1446 try:
1447 chunkedSettings[keyValue[0]] = int(keyValue[1])
1448 except ValueError:
1449 pass
1450 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1451 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1452 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001453 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001454 self.send_header('Connection', 'close')
1455 self.send_header('Transfer-Encoding', 'chunked')
1456 self.end_headers()
1457 # Chunked encoding: sending all chunks, then final zero-length chunk and
1458 # then final CRLF.
1459 for i in range(0, chunkedSettings['chunksNumber']):
1460 if i > 0:
1461 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1462 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1463 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1464 self.sendChunkHelp('')
1465 return True
1466
initial.commit94958cf2008-07-26 22:42:52 +00001467 def ContentTypeHandler(self):
1468 """Returns a string of html with the given content type. E.g.,
1469 /contenttype?text/css returns an html file with the Content-Type
1470 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001471 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001472 return False
1473 query_char = self.path.find('?')
1474 content_type = self.path[query_char + 1:].strip()
1475 if not content_type:
1476 content_type = 'text/html'
1477 self.send_response(200)
1478 self.send_header('Content-Type', content_type)
1479 self.end_headers()
1480 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1481 return True
1482
creis@google.com2f4f6a42011-03-25 19:44:19 +00001483 def NoContentHandler(self):
1484 """Returns a 204 No Content response."""
1485 if not self._ShouldHandleRequest("/nocontent"):
1486 return False
1487 self.send_response(204)
1488 self.end_headers()
1489 return True
1490
initial.commit94958cf2008-07-26 22:42:52 +00001491 def ServerRedirectHandler(self):
1492 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001493 '/server-redirect?http://foo.bar/asdf' to redirect to
1494 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001495
1496 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001497 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001498 return False
1499
1500 query_char = self.path.find('?')
1501 if query_char < 0 or len(self.path) <= query_char + 1:
1502 self.sendRedirectHelp(test_name)
1503 return True
1504 dest = self.path[query_char + 1:]
1505
1506 self.send_response(301) # moved permanently
1507 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001508 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001509 self.end_headers()
1510 self.wfile.write('<html><head>')
1511 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1512
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001513 return True
initial.commit94958cf2008-07-26 22:42:52 +00001514
1515 def ClientRedirectHandler(self):
1516 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001517 '/client-redirect?http://foo.bar/asdf' to redirect to
1518 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001519
1520 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001521 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001522 return False
1523
1524 query_char = self.path.find('?');
1525 if query_char < 0 or len(self.path) <= query_char + 1:
1526 self.sendRedirectHelp(test_name)
1527 return True
1528 dest = self.path[query_char + 1:]
1529
1530 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001531 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001532 self.end_headers()
1533 self.wfile.write('<html><head>')
1534 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1535 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1536
1537 return True
1538
tony@chromium.org03266982010-03-05 03:18:42 +00001539 def MultipartHandler(self):
1540 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001541 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001542 if not self._ShouldHandleRequest(test_name):
1543 return False
1544
1545 num_frames = 10
1546 bound = '12345'
1547 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001548 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001549 'multipart/x-mixed-replace;boundary=' + bound)
1550 self.end_headers()
1551
1552 for i in xrange(num_frames):
1553 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001554 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001555 self.wfile.write('<title>page ' + str(i) + '</title>')
1556 self.wfile.write('page ' + str(i))
1557
1558 self.wfile.write('--' + bound + '--')
1559 return True
1560
tony@chromium.org4cb88302011-09-27 22:13:49 +00001561 def MultipartSlowHandler(self):
1562 """Send a multipart response (3 text/html pages) with a slight delay
1563 between each page. This is similar to how some pages show status using
1564 multipart."""
1565 test_name = '/multipart-slow'
1566 if not self._ShouldHandleRequest(test_name):
1567 return False
1568
1569 num_frames = 3
1570 bound = '12345'
1571 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001572 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001573 'multipart/x-mixed-replace;boundary=' + bound)
1574 self.end_headers()
1575
1576 for i in xrange(num_frames):
1577 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001578 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001579 time.sleep(0.25)
1580 if i == 2:
1581 self.wfile.write('<title>PASS</title>')
1582 else:
1583 self.wfile.write('<title>page ' + str(i) + '</title>')
1584 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1585
1586 self.wfile.write('--' + bound + '--')
1587 return True
1588
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001589 def GetSSLSessionCacheHandler(self):
1590 """Send a reply containing a log of the session cache operations."""
1591
1592 if not self._ShouldHandleRequest('/ssl-session-cache'):
1593 return False
1594
1595 self.send_response(200)
1596 self.send_header('Content-Type', 'text/plain')
1597 self.end_headers()
1598 try:
1599 for (action, sessionID) in self.server.session_cache.log:
1600 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1601 except AttributeError, e:
1602 self.wfile.write('Pass --https-record-resume in order to use' +
1603 ' this request')
1604 return True
1605
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001606 def CloseSocketHandler(self):
1607 """Closes the socket without sending anything."""
1608
1609 if not self._ShouldHandleRequest('/close-socket'):
1610 return False
1611
1612 self.wfile.close()
1613 return True
1614
initial.commit94958cf2008-07-26 22:42:52 +00001615 def DefaultResponseHandler(self):
1616 """This is the catch-all response handler for requests that aren't handled
1617 by one of the special handlers above.
1618 Note that we specify the content-length as without it the https connection
1619 is not closed properly (and the browser keeps expecting data)."""
1620
1621 contents = "Default response given for path: " + self.path
1622 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001623 self.send_header('Content-Type', 'text/html')
1624 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001625 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001626 if (self.command != 'HEAD'):
1627 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001628 return True
1629
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001630 def RedirectConnectHandler(self):
1631 """Sends a redirect to the CONNECT request for www.redirect.com. This
1632 response is not specified by the RFC, so the browser should not follow
1633 the redirect."""
1634
1635 if (self.path.find("www.redirect.com") < 0):
1636 return False
1637
1638 dest = "http://www.destination.com/foo.js"
1639
1640 self.send_response(302) # moved temporarily
1641 self.send_header('Location', dest)
1642 self.send_header('Connection', 'close')
1643 self.end_headers()
1644 return True
1645
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001646 def ServerAuthConnectHandler(self):
1647 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1648 response doesn't make sense because the proxy server cannot request
1649 server authentication."""
1650
1651 if (self.path.find("www.server-auth.com") < 0):
1652 return False
1653
1654 challenge = 'Basic realm="WallyWorld"'
1655
1656 self.send_response(401) # unauthorized
1657 self.send_header('WWW-Authenticate', challenge)
1658 self.send_header('Connection', 'close')
1659 self.end_headers()
1660 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001661
1662 def DefaultConnectResponseHandler(self):
1663 """This is the catch-all response handler for CONNECT requests that aren't
1664 handled by one of the special handlers above. Real Web servers respond
1665 with 400 to CONNECT requests."""
1666
1667 contents = "Your client has issued a malformed or illegal request."
1668 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001669 self.send_header('Content-Type', 'text/html')
1670 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001671 self.end_headers()
1672 self.wfile.write(contents)
1673 return True
1674
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001675 def DeviceManagementHandler(self):
1676 """Delegates to the device management service used for cloud policy."""
1677 if not self._ShouldHandleRequest("/device_management"):
1678 return False
1679
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001680 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001681
1682 if not self.server._device_management_handler:
1683 import device_management
1684 policy_path = os.path.join(self.server.data_dir, 'device_management')
1685 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001686 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001687 self.server.policy_keys,
1688 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001689
1690 http_response, raw_reply = (
1691 self.server._device_management_handler.HandleRequest(self.path,
1692 self.headers,
1693 raw_request))
1694 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001695 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001696 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001697 self.end_headers()
1698 self.wfile.write(raw_reply)
1699 return True
1700
initial.commit94958cf2008-07-26 22:42:52 +00001701 # called by the redirect handling function when there is no parameter
1702 def sendRedirectHelp(self, redirect_name):
1703 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001704 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001705 self.end_headers()
1706 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1707 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1708 self.wfile.write('</body></html>')
1709
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001710 # called by chunked handling function
1711 def sendChunkHelp(self, chunk):
1712 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1713 self.wfile.write('%X\r\n' % len(chunk))
1714 self.wfile.write(chunk)
1715 self.wfile.write('\r\n')
1716
akalin@chromium.org154bb132010-11-12 02:20:27 +00001717
1718class SyncPageHandler(BasePageHandler):
1719 """Handler for the main HTTP sync server."""
1720
1721 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001722 get_handlers = [self.ChromiumSyncTimeHandler,
1723 self.ChromiumSyncMigrationOpHandler,
1724 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001725 self.ChromiumSyncDisableNotificationsOpHandler,
1726 self.ChromiumSyncEnableNotificationsOpHandler,
1727 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001728 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001729 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001730 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001731 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001732 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001733
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001734 post_handlers = [self.ChromiumSyncCommandHandler,
1735 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001736 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001737 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001738 post_handlers, [])
1739
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001740
akalin@chromium.org154bb132010-11-12 02:20:27 +00001741 def ChromiumSyncTimeHandler(self):
1742 """Handle Chromium sync .../time requests.
1743
1744 The syncer sometimes checks server reachability by examining /time.
1745 """
1746 test_name = "/chromiumsync/time"
1747 if not self._ShouldHandleRequest(test_name):
1748 return False
1749
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001750 # Chrome hates it if we send a response before reading the request.
1751 if self.headers.getheader('content-length'):
1752 length = int(self.headers.getheader('content-length'))
1753 raw_request = self.rfile.read(length)
1754
akalin@chromium.org154bb132010-11-12 02:20:27 +00001755 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001756 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001757 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001758 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001759 return True
1760
1761 def ChromiumSyncCommandHandler(self):
1762 """Handle a chromiumsync command arriving via http.
1763
1764 This covers all sync protocol commands: authentication, getupdates, and
1765 commit.
1766 """
1767 test_name = "/chromiumsync/command"
1768 if not self._ShouldHandleRequest(test_name):
1769 return False
1770
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001771 length = int(self.headers.getheader('content-length'))
1772 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001773 http_response = 200
1774 raw_reply = None
1775 if not self.server.GetAuthenticated():
1776 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001777 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1778 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001779 else:
1780 http_response, raw_reply = self.server.HandleCommand(
1781 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001782
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001783 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001784 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001785 if http_response == 401:
1786 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001787 self.end_headers()
1788 self.wfile.write(raw_reply)
1789 return True
1790
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001791 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001792 test_name = "/chromiumsync/migrate"
1793 if not self._ShouldHandleRequest(test_name):
1794 return False
1795
1796 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1797 self.path)
1798 self.send_response(http_response)
1799 self.send_header('Content-Type', 'text/html')
1800 self.send_header('Content-Length', len(raw_reply))
1801 self.end_headers()
1802 self.wfile.write(raw_reply)
1803 return True
1804
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001805 def ChromiumSyncCredHandler(self):
1806 test_name = "/chromiumsync/cred"
1807 if not self._ShouldHandleRequest(test_name):
1808 return False
1809 try:
1810 query = urlparse.urlparse(self.path)[4]
1811 cred_valid = urlparse.parse_qs(query)['valid']
1812 if cred_valid[0] == 'True':
1813 self.server.SetAuthenticated(True)
1814 else:
1815 self.server.SetAuthenticated(False)
1816 except:
1817 self.server.SetAuthenticated(False)
1818
1819 http_response = 200
1820 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1821 self.send_response(http_response)
1822 self.send_header('Content-Type', 'text/html')
1823 self.send_header('Content-Length', len(raw_reply))
1824 self.end_headers()
1825 self.wfile.write(raw_reply)
1826 return True
1827
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001828 def ChromiumSyncDisableNotificationsOpHandler(self):
1829 test_name = "/chromiumsync/disablenotifications"
1830 if not self._ShouldHandleRequest(test_name):
1831 return False
1832 self.server.GetXmppServer().DisableNotifications()
1833 result = 200
1834 raw_reply = ('<html><title>Notifications disabled</title>'
1835 '<H1>Notifications disabled</H1></html>')
1836 self.send_response(result)
1837 self.send_header('Content-Type', 'text/html')
1838 self.send_header('Content-Length', len(raw_reply))
1839 self.end_headers()
1840 self.wfile.write(raw_reply)
1841 return True;
1842
1843 def ChromiumSyncEnableNotificationsOpHandler(self):
1844 test_name = "/chromiumsync/enablenotifications"
1845 if not self._ShouldHandleRequest(test_name):
1846 return False
1847 self.server.GetXmppServer().EnableNotifications()
1848 result = 200
1849 raw_reply = ('<html><title>Notifications enabled</title>'
1850 '<H1>Notifications enabled</H1></html>')
1851 self.send_response(result)
1852 self.send_header('Content-Type', 'text/html')
1853 self.send_header('Content-Length', len(raw_reply))
1854 self.end_headers()
1855 self.wfile.write(raw_reply)
1856 return True;
1857
1858 def ChromiumSyncSendNotificationOpHandler(self):
1859 test_name = "/chromiumsync/sendnotification"
1860 if not self._ShouldHandleRequest(test_name):
1861 return False
1862 query = urlparse.urlparse(self.path)[4]
1863 query_params = urlparse.parse_qs(query)
1864 channel = ''
1865 data = ''
1866 if 'channel' in query_params:
1867 channel = query_params['channel'][0]
1868 if 'data' in query_params:
1869 data = query_params['data'][0]
1870 self.server.GetXmppServer().SendNotification(channel, data)
1871 result = 200
1872 raw_reply = ('<html><title>Notification sent</title>'
1873 '<H1>Notification sent with channel "%s" '
1874 'and data "%s"</H1></html>'
1875 % (channel, data))
1876 self.send_response(result)
1877 self.send_header('Content-Type', 'text/html')
1878 self.send_header('Content-Length', len(raw_reply))
1879 self.end_headers()
1880 self.wfile.write(raw_reply)
1881 return True;
1882
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001883 def ChromiumSyncBirthdayErrorOpHandler(self):
1884 test_name = "/chromiumsync/birthdayerror"
1885 if not self._ShouldHandleRequest(test_name):
1886 return False
1887 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1888 self.send_response(result)
1889 self.send_header('Content-Type', 'text/html')
1890 self.send_header('Content-Length', len(raw_reply))
1891 self.end_headers()
1892 self.wfile.write(raw_reply)
1893 return True;
1894
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001895 def ChromiumSyncTransientErrorOpHandler(self):
1896 test_name = "/chromiumsync/transienterror"
1897 if not self._ShouldHandleRequest(test_name):
1898 return False
1899 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1900 self.send_response(result)
1901 self.send_header('Content-Type', 'text/html')
1902 self.send_header('Content-Length', len(raw_reply))
1903 self.end_headers()
1904 self.wfile.write(raw_reply)
1905 return True;
1906
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001907 def ChromiumSyncErrorOpHandler(self):
1908 test_name = "/chromiumsync/error"
1909 if not self._ShouldHandleRequest(test_name):
1910 return False
1911 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1912 self.path)
1913 self.send_response(result)
1914 self.send_header('Content-Type', 'text/html')
1915 self.send_header('Content-Length', len(raw_reply))
1916 self.end_headers()
1917 self.wfile.write(raw_reply)
1918 return True;
1919
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001920 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1921 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001922 if not self._ShouldHandleRequest(test_name):
1923 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001924 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001925 self.send_response(result)
1926 self.send_header('Content-Type', 'text/html')
1927 self.send_header('Content-Length', len(raw_reply))
1928 self.end_headers()
1929 self.wfile.write(raw_reply)
1930 return True;
1931
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001932 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1933 test_name = "/chromiumsync/createsyncedbookmarks"
1934 if not self._ShouldHandleRequest(test_name):
1935 return False
1936 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1937 self.send_response(result)
1938 self.send_header('Content-Type', 'text/html')
1939 self.send_header('Content-Length', len(raw_reply))
1940 self.end_headers()
1941 self.wfile.write(raw_reply)
1942 return True;
1943
akalin@chromium.org154bb132010-11-12 02:20:27 +00001944
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001945def MakeDataDir():
1946 if options.data_dir:
1947 if not os.path.isdir(options.data_dir):
1948 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1949 return None
1950 my_data_dir = options.data_dir
1951 else:
1952 # Create the default path to our data dir, relative to the exe dir.
1953 my_data_dir = os.path.dirname(sys.argv[0])
1954 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1955 "test", "data")
1956
1957 #TODO(ibrar): Must use Find* funtion defined in google\tools
1958 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1959
1960 return my_data_dir
1961
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001962class OCSPHandler(BasePageHandler):
1963 def __init__(self, request, client_address, socket_server):
1964 handlers = [self.OCSPResponse]
1965 self.ocsp_response = socket_server.ocsp_response
1966 BasePageHandler.__init__(self, request, client_address, socket_server,
1967 [], handlers, [], handlers, [])
1968
1969 def OCSPResponse(self):
1970 self.send_response(200)
1971 self.send_header('Content-Type', 'application/ocsp-response')
1972 self.send_header('Content-Length', str(len(self.ocsp_response)))
1973 self.end_headers()
1974
1975 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001976
1977class TCPEchoHandler(SocketServer.BaseRequestHandler):
1978 """The RequestHandler class for TCP echo server.
1979
1980 It is instantiated once per connection to the server, and overrides the
1981 handle() method to implement communication to the client.
1982 """
1983
1984 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001985 """Handles the request from the client and constructs a response."""
1986
1987 data = self.request.recv(65536).strip()
1988 # Verify the "echo request" message received from the client. Send back
1989 # "echo response" message if "echo request" message is valid.
1990 try:
1991 return_data = echo_message.GetEchoResponseData(data)
1992 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001993 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001994 except ValueError:
1995 return
1996
1997 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001998
1999
2000class UDPEchoHandler(SocketServer.BaseRequestHandler):
2001 """The RequestHandler class for UDP echo server.
2002
2003 It is instantiated once per connection to the server, and overrides the
2004 handle() method to implement communication to the client.
2005 """
2006
2007 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002008 """Handles the request from the client and constructs a response."""
2009
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002010 data = self.request[0].strip()
2011 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002012 # Verify the "echo request" message received from the client. Send back
2013 # "echo response" message if "echo request" message is valid.
2014 try:
2015 return_data = echo_message.GetEchoResponseData(data)
2016 if not return_data:
2017 return
2018 except ValueError:
2019 return
2020 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002021
2022
bashi@chromium.org33233532012-09-08 17:37:24 +00002023class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2024 """A request handler that behaves as a proxy server which requires
2025 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2026 """
2027
2028 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2029
2030 def parse_request(self):
2031 """Overrides parse_request to check credential."""
2032
2033 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2034 return False
2035
2036 auth = self.headers.getheader('Proxy-Authorization')
2037 if auth != self._AUTH_CREDENTIAL:
2038 self.send_response(407)
2039 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2040 self.end_headers()
2041 return False
2042
2043 return True
2044
2045 def _start_read_write(self, sock):
2046 sock.setblocking(0)
2047 self.request.setblocking(0)
2048 rlist = [self.request, sock]
2049 while True:
2050 ready_sockets, unused, errors = select.select(rlist, [], [])
2051 if errors:
2052 self.send_response(500)
2053 self.end_headers()
2054 return
2055 for s in ready_sockets:
2056 received = s.recv(1024)
2057 if len(received) == 0:
2058 return
2059 if s == self.request:
2060 other = sock
2061 else:
2062 other = self.request
2063 other.send(received)
2064
2065 def _do_common_method(self):
2066 url = urlparse.urlparse(self.path)
2067 port = url.port
2068 if not port:
2069 if url.scheme == 'http':
2070 port = 80
2071 elif url.scheme == 'https':
2072 port = 443
2073 if not url.hostname or not port:
2074 self.send_response(400)
2075 self.end_headers()
2076 return
2077
2078 if len(url.path) == 0:
2079 path = '/'
2080 else:
2081 path = url.path
2082 if len(url.query) > 0:
2083 path = '%s?%s' % (url.path, url.query)
2084
2085 sock = None
2086 try:
2087 sock = socket.create_connection((url.hostname, port))
2088 sock.send('%s %s %s\r\n' % (
2089 self.command, path, self.protocol_version))
2090 for header in self.headers.headers:
2091 header = header.strip()
2092 if (header.lower().startswith('connection') or
2093 header.lower().startswith('proxy')):
2094 continue
2095 sock.send('%s\r\n' % header)
2096 sock.send('\r\n')
2097 self._start_read_write(sock)
2098 except:
2099 self.send_response(500)
2100 self.end_headers()
2101 finally:
2102 if sock is not None:
2103 sock.close()
2104
2105 def do_CONNECT(self):
2106 try:
2107 pos = self.path.rfind(':')
2108 host = self.path[:pos]
2109 port = int(self.path[pos+1:])
2110 except:
2111 self.send_response(400)
2112 self.end_headers()
2113
2114 try:
2115 sock = socket.create_connection((host, port))
2116 self.send_response(200, 'Connection established')
2117 self.end_headers()
2118 self._start_read_write(sock)
2119 except:
2120 self.send_response(500)
2121 self.end_headers()
2122 finally:
2123 sock.close()
2124
2125 def do_GET(self):
2126 self._do_common_method()
2127
2128 def do_HEAD(self):
2129 self._do_common_method()
2130
2131
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002132class FileMultiplexer:
2133 def __init__(self, fd1, fd2) :
2134 self.__fd1 = fd1
2135 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002136
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002137 def __del__(self) :
2138 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2139 self.__fd1.close()
2140 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2141 self.__fd2.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002142
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002143 def write(self, text) :
2144 self.__fd1.write(text)
2145 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002146
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002147 def flush(self) :
2148 self.__fd1.flush()
2149 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002150
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002151def main(options, args):
2152 logfile = open('testserver.log', 'w')
2153 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2154 if options.log_to_console:
2155 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2156 else:
2157 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002158
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002159 port = options.port
2160 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002161
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002162 server_data = {}
2163 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002164
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002165 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002166
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002167 if options.server_type == SERVER_HTTP:
2168 if options.https:
2169 pem_cert_and_key = None
2170 if options.cert_and_key_file:
2171 if not os.path.isfile(options.cert_and_key_file):
2172 print ('specified server cert file not found: ' +
2173 options.cert_and_key_file + ' exiting...')
2174 return
2175 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002176 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002177 # generate a new certificate and run an OCSP server for it.
2178 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2179 print ('OCSP server started on %s:%d...' %
2180 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002181
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002182 ocsp_der = None
2183 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002184
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002185 if options.ocsp == 'ok':
2186 ocsp_state = minica.OCSP_STATE_GOOD
2187 elif options.ocsp == 'revoked':
2188 ocsp_state = minica.OCSP_STATE_REVOKED
2189 elif options.ocsp == 'invalid':
2190 ocsp_state = minica.OCSP_STATE_INVALID
2191 elif options.ocsp == 'unauthorized':
2192 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2193 elif options.ocsp == 'unknown':
2194 ocsp_state = minica.OCSP_STATE_UNKNOWN
2195 else:
2196 print 'unknown OCSP status: ' + options.ocsp_status
2197 return
mattm@chromium.org07e28412012-09-05 00:19:41 +00002198
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002199 (pem_cert_and_key, ocsp_der) = \
2200 minica.GenerateCertKeyAndOCSP(
2201 subject = "127.0.0.1",
2202 ocsp_url = ("http://%s:%d/ocsp" %
2203 (host, ocsp_server.server_port)),
2204 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002205
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002206 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002207
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002208 for ca_cert in options.ssl_client_ca:
2209 if not os.path.isfile(ca_cert):
2210 print 'specified trusted client CA file not found: ' + ca_cert + \
2211 ' exiting...'
2212 return
2213 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2214 options.ssl_client_auth, options.ssl_client_ca,
2215 options.ssl_bulk_cipher, options.record_resume,
2216 options.tls_intolerant)
2217 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002218 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002219 server = HTTPServer((host, port), TestPageHandler)
2220 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002221
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002222 server.data_dir = MakeDataDir()
2223 server.file_root_url = options.file_root_url
2224 server_data['port'] = server.server_port
2225 server._device_management_handler = None
2226 server.policy_keys = options.policy_keys
2227 server.policy_user = options.policy_user
2228 server.gdata_auth_token = options.auth_token
2229 elif options.server_type == SERVER_SYNC:
2230 xmpp_port = options.xmpp_port
2231 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2232 print 'Sync HTTP server started on port %d...' % server.server_port
2233 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2234 server_data['port'] = server.server_port
2235 server_data['xmpp_port'] = server.xmpp_port
2236 elif options.server_type == SERVER_TCP_ECHO:
2237 # Used for generating the key (randomly) that encodes the "echo request"
2238 # message.
2239 random.seed()
2240 server = TCPEchoServer((host, port), TCPEchoHandler)
2241 print 'Echo TCP server started on port %d...' % server.server_port
2242 server_data['port'] = server.server_port
2243 elif options.server_type == SERVER_UDP_ECHO:
2244 # Used for generating the key (randomly) that encodes the "echo request"
2245 # message.
2246 random.seed()
2247 server = UDPEchoServer((host, port), UDPEchoHandler)
2248 print 'Echo UDP server started on port %d...' % server.server_port
2249 server_data['port'] = server.server_port
bashi@chromium.org33233532012-09-08 17:37:24 +00002250 elif options.server_type == SERVER_BASIC_AUTH_PROXY:
2251 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2252 print 'BasicAuthProxy server started on port %d...' % server.server_port
2253 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002254 # means FTP Server
2255 else:
2256 my_data_dir = MakeDataDir()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002257
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002258 # Instantiate a dummy authorizer for managing 'virtual' users
2259 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002260
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002261 # Define a new user having full r/w permissions and a read-only
2262 # anonymous user
2263 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002264
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002265 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002266
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002267 # Instantiate FTP handler class
2268 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2269 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002270
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002271 # Define a customized banner (string returned when client connects)
2272 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2273 pyftpdlib.ftpserver.__ver__)
2274
2275 # Instantiate FTP server class and listen to address:port
2276 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2277 server_data['port'] = server.socket.getsockname()[1]
2278 print 'FTP server started on port %d...' % server_data['port']
2279
2280 # Notify the parent that we've started. (BaseServer subclasses
2281 # bind their sockets on construction.)
2282 if options.startup_pipe is not None:
2283 server_data_json = json.dumps(server_data)
2284 server_data_len = len(server_data_json)
2285 print 'sending server_data: %s (%d bytes)' % (
2286 server_data_json, server_data_len)
2287 if sys.platform == 'win32':
2288 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2289 else:
2290 fd = options.startup_pipe
2291 startup_pipe = os.fdopen(fd, "w")
2292 # First write the data length as an unsigned 4-byte value. This
2293 # is _not_ using network byte ordering since the other end of the
2294 # pipe is on the same machine.
2295 startup_pipe.write(struct.pack('=L', server_data_len))
2296 startup_pipe.write(server_data_json)
2297 startup_pipe.close()
2298
2299 if ocsp_server is not None:
2300 ocsp_server.serve_forever_on_thread()
2301
2302 try:
2303 server.serve_forever()
2304 except KeyboardInterrupt:
2305 print 'shutting down server'
2306 if ocsp_server is not None:
2307 ocsp_server.stop_serving()
2308 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002309
2310if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002311 option_parser = optparse.OptionParser()
2312 option_parser.add_option("-f", '--ftp', action='store_const',
2313 const=SERVER_FTP, default=SERVER_HTTP,
2314 dest='server_type',
2315 help='start up an FTP server.')
2316 option_parser.add_option('', '--sync', action='store_const',
2317 const=SERVER_SYNC, default=SERVER_HTTP,
2318 dest='server_type',
2319 help='start up a sync server.')
2320 option_parser.add_option('', '--tcp-echo', action='store_const',
2321 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2322 dest='server_type',
2323 help='start up a tcp echo server.')
2324 option_parser.add_option('', '--udp-echo', action='store_const',
2325 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2326 dest='server_type',
2327 help='start up a udp echo server.')
bashi@chromium.org33233532012-09-08 17:37:24 +00002328 option_parser.add_option('', '--basic-auth-proxy', action='store_const',
2329 const=SERVER_BASIC_AUTH_PROXY, default=SERVER_HTTP,
2330 dest='server_type',
2331 help='start up a proxy server which requires basic '
2332 'authentication.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002333 option_parser.add_option('', '--log-to-console', action='store_const',
2334 const=True, default=False,
2335 dest='log_to_console',
2336 help='Enables or disables sys.stdout logging to '
2337 'the console.')
2338 option_parser.add_option('', '--port', default='0', type='int',
2339 help='Port used by the server. If unspecified, the '
2340 'server will listen on an ephemeral port.')
2341 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2342 help='Port used by the XMPP server. If unspecified, '
2343 'the XMPP server will listen on an ephemeral port.')
2344 option_parser.add_option('', '--data-dir', dest='data_dir',
2345 help='Directory from which to read the files.')
2346 option_parser.add_option('', '--https', action='store_true', dest='https',
2347 help='Specify that https should be used.')
2348 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2349 help='specify the path to the file containing the '
2350 'certificate and private key for the server in PEM '
2351 'format')
2352 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2353 help='The type of OCSP response generated for the '
2354 'automatically generated certificate. One of '
2355 '[ok,revoked,invalid]')
2356 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2357 default='0', type='int',
2358 help='If nonzero, certain TLS connections will be'
2359 ' aborted in order to test version fallback. 1'
2360 ' means all TLS versions will be aborted. 2 means'
2361 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2362 ' 1.2 or higher will be aborted.')
2363 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2364 const=True, default=False, action='store_const',
2365 help='Record resumption cache events rather than'
2366 ' resuming as normal. Allows the use of the'
2367 ' /ssl-session-cache request')
2368 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2369 help='Require SSL client auth on every connection.')
2370 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2371 help='Specify that the client certificate request '
2372 'should include the CA named in the subject of '
2373 'the DER-encoded certificate contained in the '
2374 'specified file. This option may appear multiple '
2375 'times, indicating multiple CA names should be '
2376 'sent in the request.')
2377 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2378 help='Specify the bulk encryption algorithm(s)'
2379 'that will be accepted by the SSL server. Valid '
2380 'values are "aes256", "aes128", "3des", "rc4". If '
2381 'omitted, all algorithms will be used. This '
2382 'option may appear multiple times, indicating '
2383 'multiple algorithms should be enabled.');
2384 option_parser.add_option('', '--file-root-url', default='/files/',
2385 help='Specify a root URL for files served.')
2386 option_parser.add_option('', '--startup-pipe', type='int',
2387 dest='startup_pipe',
2388 help='File handle of pipe to parent process')
2389 option_parser.add_option('', '--policy-key', action='append',
2390 dest='policy_keys',
2391 help='Specify a path to a PEM-encoded private key '
2392 'to use for policy signing. May be specified '
2393 'multiple times in order to load multipe keys into '
2394 'the server. If ther server has multiple keys, it '
2395 'will rotate through them in at each request a '
2396 'round-robin fashion. The server will generate a '
2397 'random key if none is specified on the command '
2398 'line.')
2399 option_parser.add_option('', '--policy-user', default='user@example.com',
2400 dest='policy_user',
2401 help='Specify the user name the server should '
2402 'report back to the client as the user owning the '
2403 'token used for making the policy request.')
2404 option_parser.add_option('', '--host', default='127.0.0.1',
2405 dest='host',
2406 help='Hostname or IP upon which the server will '
2407 'listen. Client connections will also only be '
2408 'allowed from this address.')
2409 option_parser.add_option('', '--auth-token', dest='auth_token',
2410 help='Specify the auth token which should be used'
2411 'in the authorization header for GData.')
2412 options, args = option_parser.parse_args()
2413
2414 sys.exit(main(options, args))