blob: aeb0575118b6761feb3069ff64c8e5f4838f2974 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000021import hashlib
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000022import httplib
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000023import json
agl@chromium.org77a9ad92012-03-20 15:14:27 +000024import minica
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000025import optparse
initial.commit94958cf2008-07-26 22:42:52 +000026import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000027import random
initial.commit94958cf2008-07-26 22:42:52 +000028import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000029import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000030import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000031import SocketServer
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000032import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000033import sys
34import threading
initial.commit94958cf2008-07-26 22:42:52 +000035import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000036import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000037import urlparse
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000038import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000039import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000040
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000041# Ignore deprecation warnings, they make our output more cluttered.
42warnings.filterwarnings("ignore", category=DeprecationWarning)
43
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000044import echo_message
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000045import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000046import tlslite
47import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000048
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000049if sys.platform == 'win32':
50 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000051
maruel@chromium.org756cf982009-03-05 12:46:38 +000052SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000053SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000054SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000055SERVER_TCP_ECHO = 3
56SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000057SERVER_BASIC_AUTH_PROXY = 5
initial.commit94958cf2008-07-26 22:42:52 +000058
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000059# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000060debug_output = sys.stderr
61def debug(str):
62 debug_output.write(str + "\n")
63 debug_output.flush()
64
agl@chromium.orgf9e66792011-12-12 22:22:19 +000065class RecordingSSLSessionCache(object):
66 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
67 lookups and inserts in order to test session cache behaviours."""
68
69 def __init__(self):
70 self.log = []
71
72 def __getitem__(self, sessionID):
73 self.log.append(('lookup', sessionID))
74 raise KeyError()
75
76 def __setitem__(self, sessionID, session):
77 self.log.append(('insert', sessionID))
78
erikwright@chromium.org847ef282012-02-22 16:41:10 +000079
80class ClientRestrictingServerMixIn:
81 """Implements verify_request to limit connections to our configured IP
82 address."""
83
84 def verify_request(self, request, client_address):
85 return client_address[0] == self.server_address[0]
86
87
initial.commit94958cf2008-07-26 22:42:52 +000088class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +000089 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +000090 to be exited cleanly (by setting its "stop" member to True)."""
91
92 def serve_forever(self):
93 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +000094 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +000095 while not self.stop:
96 self.handle_request()
97 self.socket.close()
98
erikwright@chromium.org847ef282012-02-22 16:41:10 +000099
100class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000101 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000102 verification."""
103
104 pass
105
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000106class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
107 """This is a specialization of HTTPServer that serves an
108 OCSP response"""
109
110 def serve_forever_on_thread(self):
111 self.thread = threading.Thread(target = self.serve_forever,
112 name = "OCSPServerThread")
113 self.thread.start()
114
115 def stop_serving(self):
116 self.shutdown()
117 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000118
119class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
120 ClientRestrictingServerMixIn,
121 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000122 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000124
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000125 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000126 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000127 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000128 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
129 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000130 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000131 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000132 self.tls_intolerant = tls_intolerant
133
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000134 for ca_file in ssl_client_cas:
135 s = open(ca_file).read()
136 x509 = tlslite.api.X509()
137 x509.parse(s)
138 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000139 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
140 if ssl_bulk_ciphers is not None:
141 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000142
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000143 if record_resume_info:
144 # If record_resume_info is true then we'll replace the session cache with
145 # an object that records the lookups and inserts that it sees.
146 self.session_cache = RecordingSSLSessionCache()
147 else:
148 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000149 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
150
151 def handshake(self, tlsConnection):
152 """Creates the SSL connection."""
153 try:
154 tlsConnection.handshakeServer(certChain=self.cert_chain,
155 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000156 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000157 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000158 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000159 reqCAs=self.ssl_client_cas,
160 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000161 tlsConnection.ignoreAbruptClose = True
162 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000163 except tlslite.api.TLSAbruptCloseError:
164 # Ignore abrupt close.
165 return True
initial.commit94958cf2008-07-26 22:42:52 +0000166 except tlslite.api.TLSError, error:
167 print "Handshake failure:", str(error)
168 return False
169
akalin@chromium.org154bb132010-11-12 02:20:27 +0000170
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000171class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000172 """An HTTP server that handles sync commands."""
173
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000174 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000175 # We import here to avoid pulling in chromiumsync's dependencies
176 # unless strictly necessary.
177 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000178 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000179 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000180 self._sync_handler = chromiumsync.TestServer()
181 self._xmpp_socket_map = {}
182 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000183 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000184 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000185 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000186
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000187 def GetXmppServer(self):
188 return self._xmpp_server
189
akalin@chromium.org154bb132010-11-12 02:20:27 +0000190 def HandleCommand(self, query, raw_request):
191 return self._sync_handler.HandleCommand(query, raw_request)
192
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000193 def HandleRequestNoBlock(self):
194 """Handles a single request.
195
196 Copied from SocketServer._handle_request_noblock().
197 """
198 try:
199 request, client_address = self.get_request()
200 except socket.error:
201 return
202 if self.verify_request(request, client_address):
203 try:
204 self.process_request(request, client_address)
205 except:
206 self.handle_error(request, client_address)
207 self.close_request(request)
208
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000209 def SetAuthenticated(self, auth_valid):
210 self.authenticated = auth_valid
211
212 def GetAuthenticated(self):
213 return self.authenticated
214
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000215 def serve_forever(self):
216 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
217 """
218
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000219 def HandleXmppSocket(fd, socket_map, handler):
220 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000221
222 Adapted from asyncore.read() et al.
223 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000224 xmpp_connection = socket_map.get(fd)
225 # This could happen if a previous handler call caused fd to get
226 # removed from socket_map.
227 if xmpp_connection is None:
228 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000229 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000230 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000231 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
232 raise
233 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000234 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000235
236 while True:
237 read_fds = [ self.fileno() ]
238 write_fds = []
239 exceptional_fds = []
240
241 for fd, xmpp_connection in self._xmpp_socket_map.items():
242 is_r = xmpp_connection.readable()
243 is_w = xmpp_connection.writable()
244 if is_r:
245 read_fds.append(fd)
246 if is_w:
247 write_fds.append(fd)
248 if is_r or is_w:
249 exceptional_fds.append(fd)
250
251 try:
252 read_fds, write_fds, exceptional_fds = (
253 select.select(read_fds, write_fds, exceptional_fds))
254 except select.error, err:
255 if err.args[0] != errno.EINTR:
256 raise
257 else:
258 continue
259
260 for fd in read_fds:
261 if fd == self.fileno():
262 self.HandleRequestNoBlock()
263 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000264 HandleXmppSocket(fd, self._xmpp_socket_map,
265 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000266
267 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000268 HandleXmppSocket(fd, self._xmpp_socket_map,
269 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000270
271 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000272 HandleXmppSocket(fd, self._xmpp_socket_map,
273 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000274
akalin@chromium.org154bb132010-11-12 02:20:27 +0000275
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000276class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
277 """This is a specialization of FTPServer that adds client verification."""
278
279 pass
280
281
282class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000283 """A TCP echo server that echoes back what it has received."""
284
285 def server_bind(self):
286 """Override server_bind to store the server name."""
287 SocketServer.TCPServer.server_bind(self)
288 host, port = self.socket.getsockname()[:2]
289 self.server_name = socket.getfqdn(host)
290 self.server_port = port
291
292 def serve_forever(self):
293 self.stop = False
294 self.nonce_time = None
295 while not self.stop:
296 self.handle_request()
297 self.socket.close()
298
299
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000300class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000301 """A UDP echo server that echoes back what it has received."""
302
303 def server_bind(self):
304 """Override server_bind to store the server name."""
305 SocketServer.UDPServer.server_bind(self)
306 host, port = self.socket.getsockname()[:2]
307 self.server_name = socket.getfqdn(host)
308 self.server_port = port
309
310 def serve_forever(self):
311 self.stop = False
312 self.nonce_time = None
313 while not self.stop:
314 self.handle_request()
315 self.socket.close()
316
317
akalin@chromium.org154bb132010-11-12 02:20:27 +0000318class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
319
320 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000321 connect_handlers, get_handlers, head_handlers, post_handlers,
322 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000323 self._connect_handlers = connect_handlers
324 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000325 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000326 self._post_handlers = post_handlers
327 self._put_handlers = put_handlers
328 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
329 self, request, client_address, socket_server)
330
331 def log_request(self, *args, **kwargs):
332 # Disable request logging to declutter test log output.
333 pass
334
335 def _ShouldHandleRequest(self, handler_name):
336 """Determines if the path can be handled by the handler.
337
338 We consider a handler valid if the path begins with the
339 handler name. It can optionally be followed by "?*", "/*".
340 """
341
342 pattern = re.compile('%s($|\?|/).*' % handler_name)
343 return pattern.match(self.path)
344
345 def do_CONNECT(self):
346 for handler in self._connect_handlers:
347 if handler():
348 return
349
350 def do_GET(self):
351 for handler in self._get_handlers:
352 if handler():
353 return
354
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000355 def do_HEAD(self):
356 for handler in self._head_handlers:
357 if handler():
358 return
359
akalin@chromium.org154bb132010-11-12 02:20:27 +0000360 def do_POST(self):
361 for handler in self._post_handlers:
362 if handler():
363 return
364
365 def do_PUT(self):
366 for handler in self._put_handlers:
367 if handler():
368 return
369
370
371class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000372
373 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000374 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000375 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000376 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000377 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000378 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000379 self.NoCacheMaxAgeTimeHandler,
380 self.NoCacheTimeHandler,
381 self.CacheTimeHandler,
382 self.CacheExpiresHandler,
383 self.CacheProxyRevalidateHandler,
384 self.CachePrivateHandler,
385 self.CachePublicHandler,
386 self.CacheSMaxAgeHandler,
387 self.CacheMustRevalidateHandler,
388 self.CacheMustRevalidateMaxAgeHandler,
389 self.CacheNoStoreHandler,
390 self.CacheNoStoreMaxAgeHandler,
391 self.CacheNoTransformHandler,
392 self.DownloadHandler,
393 self.DownloadFinishHandler,
394 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000395 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000396 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000397 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000398 self.GDataAuthHandler,
399 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000400 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000401 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000402 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000403 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000404 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000405 self.AuthBasicHandler,
406 self.AuthDigestHandler,
407 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000408 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000409 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000410 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000411 self.ServerRedirectHandler,
412 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000413 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000414 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000415 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000416 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000417 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000418 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000419 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000420 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000421 self.DeviceManagementHandler,
422 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000423 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000424 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000425 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000426 head_handlers = [
427 self.FileHandler,
428 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000429
maruel@google.come250a9b2009-03-10 17:39:46 +0000430 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000431 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000432 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000433 'gif': 'image/gif',
434 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000435 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000436 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000437 'pdf' : 'application/pdf',
438 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000439 }
initial.commit94958cf2008-07-26 22:42:52 +0000440 self._default_mime_type = 'text/html'
441
akalin@chromium.org154bb132010-11-12 02:20:27 +0000442 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000443 connect_handlers, get_handlers, head_handlers,
444 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000445
initial.commit94958cf2008-07-26 22:42:52 +0000446 def GetMIMETypeFromName(self, file_name):
447 """Returns the mime type for the specified file_name. So far it only looks
448 at the file extension."""
449
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000450 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000451 if len(extension) == 0:
452 # no extension.
453 return self._default_mime_type
454
ericroman@google.comc17ca532009-05-07 03:51:05 +0000455 # extension starts with a dot, so we need to remove it
456 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000457
initial.commit94958cf2008-07-26 22:42:52 +0000458 def NoCacheMaxAgeTimeHandler(self):
459 """This request handler yields a page with the title set to the current
460 system time, and no caching requested."""
461
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000462 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000463 return False
464
465 self.send_response(200)
466 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000467 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000468 self.end_headers()
469
maruel@google.come250a9b2009-03-10 17:39:46 +0000470 self.wfile.write('<html><head><title>%s</title></head></html>' %
471 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000472
473 return True
474
475 def NoCacheTimeHandler(self):
476 """This request handler yields a page with the title set to the current
477 system time, and no caching requested."""
478
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000480 return False
481
482 self.send_response(200)
483 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000484 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000485 self.end_headers()
486
maruel@google.come250a9b2009-03-10 17:39:46 +0000487 self.wfile.write('<html><head><title>%s</title></head></html>' %
488 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000489
490 return True
491
492 def CacheTimeHandler(self):
493 """This request handler yields a page with the title set to the current
494 system time, and allows caching for one minute."""
495
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000496 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000497 return False
498
499 self.send_response(200)
500 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000501 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000502 self.end_headers()
503
maruel@google.come250a9b2009-03-10 17:39:46 +0000504 self.wfile.write('<html><head><title>%s</title></head></html>' %
505 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000506
507 return True
508
509 def CacheExpiresHandler(self):
510 """This request handler yields a page with the title set to the current
511 system time, and set the page to expire on 1 Jan 2099."""
512
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000513 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000514 return False
515
516 self.send_response(200)
517 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000518 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000519 self.end_headers()
520
maruel@google.come250a9b2009-03-10 17:39:46 +0000521 self.wfile.write('<html><head><title>%s</title></head></html>' %
522 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000523
524 return True
525
526 def CacheProxyRevalidateHandler(self):
527 """This request handler yields a page with the title set to the current
528 system time, and allows caching for 60 seconds"""
529
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000530 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000531 return False
532
533 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000534 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000535 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
536 self.end_headers()
537
maruel@google.come250a9b2009-03-10 17:39:46 +0000538 self.wfile.write('<html><head><title>%s</title></head></html>' %
539 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000540
541 return True
542
543 def CachePrivateHandler(self):
544 """This request handler yields a page with the title set to the current
545 system time, and allows caching for 5 seconds."""
546
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000547 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000548 return False
549
550 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000551 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000552 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000553 self.end_headers()
554
maruel@google.come250a9b2009-03-10 17:39:46 +0000555 self.wfile.write('<html><head><title>%s</title></head></html>' %
556 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000557
558 return True
559
560 def CachePublicHandler(self):
561 """This request handler yields a page with the title set to the current
562 system time, and allows caching for 5 seconds."""
563
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000564 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000565 return False
566
567 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000568 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000569 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000570 self.end_headers()
571
maruel@google.come250a9b2009-03-10 17:39:46 +0000572 self.wfile.write('<html><head><title>%s</title></head></html>' %
573 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000574
575 return True
576
577 def CacheSMaxAgeHandler(self):
578 """This request handler yields a page with the title set to the current
579 system time, and does not allow for caching."""
580
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000581 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000582 return False
583
584 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000585 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000586 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
587 self.end_headers()
588
maruel@google.come250a9b2009-03-10 17:39:46 +0000589 self.wfile.write('<html><head><title>%s</title></head></html>' %
590 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000591
592 return True
593
594 def CacheMustRevalidateHandler(self):
595 """This request handler yields a page with the title set to the current
596 system time, and does not allow caching."""
597
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000598 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000599 return False
600
601 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000602 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000603 self.send_header('Cache-Control', 'must-revalidate')
604 self.end_headers()
605
maruel@google.come250a9b2009-03-10 17:39:46 +0000606 self.wfile.write('<html><head><title>%s</title></head></html>' %
607 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000608
609 return True
610
611 def CacheMustRevalidateMaxAgeHandler(self):
612 """This request handler yields a page with the title set to the current
613 system time, and does not allow caching event though max-age of 60
614 seconds is specified."""
615
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000616 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000617 return False
618
619 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000620 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000621 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
622 self.end_headers()
623
maruel@google.come250a9b2009-03-10 17:39:46 +0000624 self.wfile.write('<html><head><title>%s</title></head></html>' %
625 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000626
627 return True
628
initial.commit94958cf2008-07-26 22:42:52 +0000629 def CacheNoStoreHandler(self):
630 """This request handler yields a page with the title set to the current
631 system time, and does not allow the page to be stored."""
632
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000633 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000634 return False
635
636 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000637 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000638 self.send_header('Cache-Control', 'no-store')
639 self.end_headers()
640
maruel@google.come250a9b2009-03-10 17:39:46 +0000641 self.wfile.write('<html><head><title>%s</title></head></html>' %
642 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000643
644 return True
645
646 def CacheNoStoreMaxAgeHandler(self):
647 """This request handler yields a page with the title set to the current
648 system time, and does not allow the page to be stored even though max-age
649 of 60 seconds is specified."""
650
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000651 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000652 return False
653
654 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000655 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000656 self.send_header('Cache-Control', 'max-age=60, no-store')
657 self.end_headers()
658
maruel@google.come250a9b2009-03-10 17:39:46 +0000659 self.wfile.write('<html><head><title>%s</title></head></html>' %
660 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000661
662 return True
663
664
665 def CacheNoTransformHandler(self):
666 """This request handler yields a page with the title set to the current
667 system time, and does not allow the content to transformed during
668 user-agent caching"""
669
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000670 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000671 return False
672
673 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000674 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000675 self.send_header('Cache-Control', 'no-transform')
676 self.end_headers()
677
maruel@google.come250a9b2009-03-10 17:39:46 +0000678 self.wfile.write('<html><head><title>%s</title></head></html>' %
679 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000680
681 return True
682
683 def EchoHeader(self):
684 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000685 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000686
ananta@chromium.org56812d02011-04-07 17:52:05 +0000687 """This function echoes back the value of a specific request header"""
688 """while allowing caching for 16 hours."""
689 def EchoHeaderCache(self):
690 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000691
692 def EchoHeaderHelper(self, echo_header):
693 """This function echoes back the value of the request header passed in."""
694 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000695 return False
696
697 query_char = self.path.find('?')
698 if query_char != -1:
699 header_name = self.path[query_char+1:]
700
701 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000702 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000703 if echo_header == '/echoheadercache':
704 self.send_header('Cache-control', 'max-age=60000')
705 else:
706 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000707 # insert a vary header to properly indicate that the cachability of this
708 # request is subject to value of the request header being echoed.
709 if len(header_name) > 0:
710 self.send_header('Vary', header_name)
711 self.end_headers()
712
713 if len(header_name) > 0:
714 self.wfile.write(self.headers.getheader(header_name))
715
716 return True
717
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000718 def ReadRequestBody(self):
719 """This function reads the body of the current HTTP request, handling
720 both plain and chunked transfer encoded requests."""
721
722 if self.headers.getheader('transfer-encoding') != 'chunked':
723 length = int(self.headers.getheader('content-length'))
724 return self.rfile.read(length)
725
726 # Read the request body as chunks.
727 body = ""
728 while True:
729 line = self.rfile.readline()
730 length = int(line, 16)
731 if length == 0:
732 self.rfile.readline()
733 break
734 body += self.rfile.read(length)
735 self.rfile.read(2)
736 return body
737
initial.commit94958cf2008-07-26 22:42:52 +0000738 def EchoHandler(self):
739 """This handler just echoes back the payload of the request, for testing
740 form submission."""
741
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000742 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000743 return False
744
745 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000746 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000747 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000748 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000749 return True
750
751 def EchoTitleHandler(self):
752 """This handler is like Echo, but sets the page title to the request."""
753
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000754 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000755 return False
756
757 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000758 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000759 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000760 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000761 self.wfile.write('<html><head><title>')
762 self.wfile.write(request)
763 self.wfile.write('</title></head></html>')
764 return True
765
766 def EchoAllHandler(self):
767 """This handler yields a (more) human-readable page listing information
768 about the request header & contents."""
769
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000770 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000771 return False
772
773 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000774 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000775 self.end_headers()
776 self.wfile.write('<html><head><style>'
777 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
778 '</style></head><body>'
779 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000780 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000781 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000782
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000783 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000784 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000785 params = cgi.parse_qs(qs, keep_blank_values=1)
786
787 for param in params:
788 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000789
790 self.wfile.write('</pre>')
791
792 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
793
794 self.wfile.write('</body></html>')
795 return True
796
797 def DownloadHandler(self):
798 """This handler sends a downloadable file with or without reporting
799 the size (6K)."""
800
801 if self.path.startswith("/download-unknown-size"):
802 send_length = False
803 elif self.path.startswith("/download-known-size"):
804 send_length = True
805 else:
806 return False
807
808 #
809 # The test which uses this functionality is attempting to send
810 # small chunks of data to the client. Use a fairly large buffer
811 # so that we'll fill chrome's IO buffer enough to force it to
812 # actually write the data.
813 # See also the comments in the client-side of this test in
814 # download_uitest.cc
815 #
816 size_chunk1 = 35*1024
817 size_chunk2 = 10*1024
818
819 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000820 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000821 self.send_header('Cache-Control', 'max-age=0')
822 if send_length:
823 self.send_header('Content-Length', size_chunk1 + size_chunk2)
824 self.end_headers()
825
826 # First chunk of data:
827 self.wfile.write("*" * size_chunk1)
828 self.wfile.flush()
829
830 # handle requests until one of them clears this flag.
831 self.server.waitForDownload = True
832 while self.server.waitForDownload:
833 self.server.handle_request()
834
835 # Second chunk of data:
836 self.wfile.write("*" * size_chunk2)
837 return True
838
839 def DownloadFinishHandler(self):
840 """This handler just tells the server to finish the current download."""
841
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000842 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000843 return False
844
845 self.server.waitForDownload = False
846 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000847 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000848 self.send_header('Cache-Control', 'max-age=0')
849 self.end_headers()
850 return True
851
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000852 def _ReplaceFileData(self, data, query_parameters):
853 """Replaces matching substrings in a file.
854
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000855 If the 'replace_text' URL query parameter is present, it is expected to be
856 of the form old_text:new_text, which indicates that any old_text strings in
857 the file are replaced with new_text. Multiple 'replace_text' parameters may
858 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000859
860 If the parameters are not present, |data| is returned.
861 """
862 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000863 replace_text_values = query_dict.get('replace_text', [])
864 for replace_text_value in replace_text_values:
865 replace_text_args = replace_text_value.split(':')
866 if len(replace_text_args) != 2:
867 raise ValueError(
868 'replace_text must be of form old_text:new_text. Actual value: %s' %
869 replace_text_value)
870 old_text_b64, new_text_b64 = replace_text_args
871 old_text = base64.urlsafe_b64decode(old_text_b64)
872 new_text = base64.urlsafe_b64decode(new_text_b64)
873 data = data.replace(old_text, new_text)
874 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000875
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000876 def ZipFileHandler(self):
877 """This handler sends the contents of the requested file in compressed form.
878 Can pass in a parameter that specifies that the content length be
879 C - the compressed size (OK),
880 U - the uncompressed size (Non-standard, but handled),
881 S - less than compressed (OK because we keep going),
882 M - larger than compressed but less than uncompressed (an error),
883 L - larger than uncompressed (an error)
884 Example: compressedfiles/Picture_1.doc?C
885 """
886
887 prefix = "/compressedfiles/"
888 if not self.path.startswith(prefix):
889 return False
890
891 # Consume a request body if present.
892 if self.command == 'POST' or self.command == 'PUT' :
893 self.ReadRequestBody()
894
895 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
896
897 if not query in ('C', 'U', 'S', 'M', 'L'):
898 return False
899
900 sub_path = url_path[len(prefix):]
901 entries = sub_path.split('/')
902 file_path = os.path.join(self.server.data_dir, *entries)
903 if os.path.isdir(file_path):
904 file_path = os.path.join(file_path, 'index.html')
905
906 if not os.path.isfile(file_path):
907 print "File not found " + sub_path + " full path:" + file_path
908 self.send_error(404)
909 return True
910
911 f = open(file_path, "rb")
912 data = f.read()
913 uncompressed_len = len(data)
914 f.close()
915
916 # Compress the data.
917 data = zlib.compress(data)
918 compressed_len = len(data)
919
920 content_length = compressed_len
921 if query == 'U':
922 content_length = uncompressed_len
923 elif query == 'S':
924 content_length = compressed_len / 2
925 elif query == 'M':
926 content_length = (compressed_len + uncompressed_len) / 2
927 elif query == 'L':
928 content_length = compressed_len + uncompressed_len
929
930 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000931 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000932 self.send_header('Content-encoding', 'deflate')
933 self.send_header('Connection', 'close')
934 self.send_header('Content-Length', content_length)
935 self.send_header('ETag', '\'' + file_path + '\'')
936 self.end_headers()
937
938 self.wfile.write(data)
939
940 return True
941
initial.commit94958cf2008-07-26 22:42:52 +0000942 def FileHandler(self):
943 """This handler sends the contents of the requested file. Wow, it's like
944 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000945 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000946 if not self.path.startswith(prefix):
947 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000948 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000949
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000950 def PostOnlyFileHandler(self):
951 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000952 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000953 if not self.path.startswith(prefix):
954 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000955 return self._FileHandlerHelper(prefix)
956
957 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000958 request_body = ''
959 if self.command == 'POST' or self.command == 'PUT':
960 # Consume a request body if present.
961 request_body = self.ReadRequestBody()
962
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000963 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000964 query_dict = cgi.parse_qs(query)
965
966 expected_body = query_dict.get('expected_body', [])
967 if expected_body and request_body not in expected_body:
968 self.send_response(404)
969 self.end_headers()
970 self.wfile.write('')
971 return True
972
973 expected_headers = query_dict.get('expected_headers', [])
974 for expected_header in expected_headers:
975 header_name, expected_value = expected_header.split(':')
976 if self.headers.getheader(header_name) != expected_value:
977 self.send_response(404)
978 self.end_headers()
979 self.wfile.write('')
980 return True
981
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000982 sub_path = url_path[len(prefix):]
983 entries = sub_path.split('/')
984 file_path = os.path.join(self.server.data_dir, *entries)
985 if os.path.isdir(file_path):
986 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000987
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000988 if not os.path.isfile(file_path):
989 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000990 self.send_error(404)
991 return True
992
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000993 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000994 data = f.read()
995 f.close()
996
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000997 data = self._ReplaceFileData(data, query)
998
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000999 old_protocol_version = self.protocol_version
1000
initial.commit94958cf2008-07-26 22:42:52 +00001001 # If file.mock-http-headers exists, it contains the headers we
1002 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001003 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001004 if os.path.isfile(headers_path):
1005 f = open(headers_path, "r")
1006
1007 # "HTTP/1.1 200 OK"
1008 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001009 http_major, http_minor, status_code = re.findall(
1010 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1011 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001012 self.send_response(int(status_code))
1013
1014 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001015 header_values = re.findall('(\S+):\s*(.*)', line)
1016 if len(header_values) > 0:
1017 # "name: value"
1018 name, value = header_values[0]
1019 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001020 f.close()
1021 else:
1022 # Could be more generic once we support mime-type sniffing, but for
1023 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001024
1025 range = self.headers.get('Range')
1026 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001027 # Note this doesn't handle all valid byte range values (i.e. left
1028 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001029 range = range[6:].split('-')
1030 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001031 if range[1]:
1032 end = int(range[1])
1033 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001034 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001035
1036 self.send_response(206)
1037 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1038 str(len(data))
1039 self.send_header('Content-Range', content_range)
1040 data = data[start: end + 1]
1041 else:
1042 self.send_response(200)
1043
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001044 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001045 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001046 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001047 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001048 self.end_headers()
1049
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001050 if (self.command != 'HEAD'):
1051 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001052
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001053 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001054 return True
1055
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001056 def SetCookieHandler(self):
1057 """This handler just sets a cookie, for testing cookie handling."""
1058
1059 if not self._ShouldHandleRequest("/set-cookie"):
1060 return False
1061
1062 query_char = self.path.find('?')
1063 if query_char != -1:
1064 cookie_values = self.path[query_char + 1:].split('&')
1065 else:
1066 cookie_values = ("",)
1067 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001068 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001069 for cookie_value in cookie_values:
1070 self.send_header('Set-Cookie', '%s' % cookie_value)
1071 self.end_headers()
1072 for cookie_value in cookie_values:
1073 self.wfile.write('%s' % cookie_value)
1074 return True
1075
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001076 def SetManyCookiesHandler(self):
1077 """This handler just sets a given number of cookies, for testing handling
1078 of large numbers of cookies."""
1079
1080 if not self._ShouldHandleRequest("/set-many-cookies"):
1081 return False
1082
1083 query_char = self.path.find('?')
1084 if query_char != -1:
1085 num_cookies = int(self.path[query_char + 1:])
1086 else:
1087 num_cookies = 0
1088 self.send_response(200)
1089 self.send_header('', 'text/html')
1090 for i in range(0, num_cookies):
1091 self.send_header('Set-Cookie', 'a=')
1092 self.end_headers()
1093 self.wfile.write('%d cookies were sent' % num_cookies)
1094 return True
1095
mattm@chromium.org983fc462012-06-30 00:52:08 +00001096 def ExpectAndSetCookieHandler(self):
1097 """Expects some cookies to be sent, and if they are, sets more cookies.
1098
1099 The expect parameter specifies a required cookie. May be specified multiple
1100 times.
1101 The set parameter specifies a cookie to set if all required cookies are
1102 preset. May be specified multiple times.
1103 The data parameter specifies the response body data to be returned."""
1104
1105 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1106 return False
1107
1108 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1109 query_dict = cgi.parse_qs(query)
1110 cookies = set()
1111 if 'Cookie' in self.headers:
1112 cookie_header = self.headers.getheader('Cookie')
1113 cookies.update([s.strip() for s in cookie_header.split(';')])
1114 got_all_expected_cookies = True
1115 for expected_cookie in query_dict.get('expect', []):
1116 if expected_cookie not in cookies:
1117 got_all_expected_cookies = False
1118 self.send_response(200)
1119 self.send_header('Content-Type', 'text/html')
1120 if got_all_expected_cookies:
1121 for cookie_value in query_dict.get('set', []):
1122 self.send_header('Set-Cookie', '%s' % cookie_value)
1123 self.end_headers()
1124 for data_value in query_dict.get('data', []):
1125 self.wfile.write(data_value)
1126 return True
1127
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001128 def SetHeaderHandler(self):
1129 """This handler sets a response header. Parameters are in the
1130 key%3A%20value&key2%3A%20value2 format."""
1131
1132 if not self._ShouldHandleRequest("/set-header"):
1133 return False
1134
1135 query_char = self.path.find('?')
1136 if query_char != -1:
1137 headers_values = self.path[query_char + 1:].split('&')
1138 else:
1139 headers_values = ("",)
1140 self.send_response(200)
1141 self.send_header('Content-Type', 'text/html')
1142 for header_value in headers_values:
1143 header_value = urllib.unquote(header_value)
1144 (key, value) = header_value.split(': ', 1)
1145 self.send_header(key, value)
1146 self.end_headers()
1147 for header_value in headers_values:
1148 self.wfile.write('%s' % header_value)
1149 return True
1150
initial.commit94958cf2008-07-26 22:42:52 +00001151 def AuthBasicHandler(self):
1152 """This handler tests 'Basic' authentication. It just sends a page with
1153 title 'user/pass' if you succeed."""
1154
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001155 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001156 return False
1157
1158 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001159 expected_password = 'secret'
1160 realm = 'testrealm'
1161 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001162
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001163 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1164 query_params = cgi.parse_qs(query, True)
1165 if 'set-cookie-if-challenged' in query_params:
1166 set_cookie_if_challenged = True
1167 if 'password' in query_params:
1168 expected_password = query_params['password'][0]
1169 if 'realm' in query_params:
1170 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001171
initial.commit94958cf2008-07-26 22:42:52 +00001172 auth = self.headers.getheader('authorization')
1173 try:
1174 if not auth:
1175 raise Exception('no auth')
1176 b64str = re.findall(r'Basic (\S+)', auth)[0]
1177 userpass = base64.b64decode(b64str)
1178 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001179 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001180 raise Exception('wrong password')
1181 except Exception, e:
1182 # Authentication failed.
1183 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001184 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001185 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001186 if set_cookie_if_challenged:
1187 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001188 self.end_headers()
1189 self.wfile.write('<html><head>')
1190 self.wfile.write('<title>Denied: %s</title>' % e)
1191 self.wfile.write('</head><body>')
1192 self.wfile.write('auth=%s<p>' % auth)
1193 self.wfile.write('b64str=%s<p>' % b64str)
1194 self.wfile.write('username: %s<p>' % username)
1195 self.wfile.write('userpass: %s<p>' % userpass)
1196 self.wfile.write('password: %s<p>' % password)
1197 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1198 self.wfile.write('</body></html>')
1199 return True
1200
1201 # Authentication successful. (Return a cachable response to allow for
1202 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001203 old_protocol_version = self.protocol_version
1204 self.protocol_version = "HTTP/1.1"
1205
initial.commit94958cf2008-07-26 22:42:52 +00001206 if_none_match = self.headers.getheader('if-none-match')
1207 if if_none_match == "abc":
1208 self.send_response(304)
1209 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001210 elif url_path.endswith(".gif"):
1211 # Using chrome/test/data/google/logo.gif as the test image
1212 test_image_path = ['google', 'logo.gif']
1213 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1214 if not os.path.isfile(gif_path):
1215 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001216 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001217 return True
1218
1219 f = open(gif_path, "rb")
1220 data = f.read()
1221 f.close()
1222
1223 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001224 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001225 self.send_header('Cache-control', 'max-age=60000')
1226 self.send_header('Etag', 'abc')
1227 self.end_headers()
1228 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001229 else:
1230 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001231 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001232 self.send_header('Cache-control', 'max-age=60000')
1233 self.send_header('Etag', 'abc')
1234 self.end_headers()
1235 self.wfile.write('<html><head>')
1236 self.wfile.write('<title>%s/%s</title>' % (username, password))
1237 self.wfile.write('</head><body>')
1238 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001239 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001240 self.wfile.write('</body></html>')
1241
rvargas@google.com54453b72011-05-19 01:11:11 +00001242 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001243 return True
1244
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001245 def GDataAuthHandler(self):
1246 """This handler verifies the Authentication header for GData requests."""
1247 if not self.server.gdata_auth_token:
1248 # --auth-token is not specified, not the test case for GData.
1249 return False
1250
1251 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1252 return False
1253
1254 if 'GData-Version' not in self.headers:
1255 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1256 return True
1257
1258 if 'Authorization' not in self.headers:
1259 self.send_error(httplib.UNAUTHORIZED)
1260 return True
1261
1262 field_prefix = 'Bearer '
1263 authorization = self.headers['Authorization']
1264 if not authorization.startswith(field_prefix):
1265 self.send_error(httplib.UNAUTHORIZED)
1266 return True
1267
1268 code = authorization[len(field_prefix):]
1269 if code != self.server.gdata_auth_token:
1270 self.send_error(httplib.UNAUTHORIZED)
1271 return True
1272
1273 return False
1274
1275 def GDataDocumentsFeedQueryHandler(self):
1276 """This handler verifies if required parameters are properly
1277 specified for the GData DocumentsFeed request."""
1278 if not self.server.gdata_auth_token:
1279 # --auth-token is not specified, not the test case for GData.
1280 return False
1281
1282 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1283 return False
1284
1285 (path, question, query_params) = self.path.partition('?')
1286 self.query_params = urlparse.parse_qs(query_params)
1287
1288 if 'v' not in self.query_params:
1289 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1290 return True
1291 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1292 # currently our GData client only uses JSON format.
1293 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1294 return True
1295
1296 return False
1297
tonyg@chromium.org75054202010-03-31 22:06:10 +00001298 def GetNonce(self, force_reset=False):
1299 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001300
tonyg@chromium.org75054202010-03-31 22:06:10 +00001301 This is a fake implementation. A real implementation would only use a given
1302 nonce a single time (hence the name n-once). However, for the purposes of
1303 unittesting, we don't care about the security of the nonce.
1304
1305 Args:
1306 force_reset: Iff set, the nonce will be changed. Useful for testing the
1307 "stale" response.
1308 """
1309 if force_reset or not self.server.nonce_time:
1310 self.server.nonce_time = time.time()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001311 return hashlib.md5('privatekey%s%d' %
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001312 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001313
1314 def AuthDigestHandler(self):
1315 """This handler tests 'Digest' authentication.
1316
1317 It just sends a page with title 'user/pass' if you succeed.
1318
1319 A stale response is sent iff "stale" is present in the request path.
1320 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001321 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001322 return False
1323
tonyg@chromium.org75054202010-03-31 22:06:10 +00001324 stale = 'stale' in self.path
1325 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001326 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001327 password = 'secret'
1328 realm = 'testrealm'
1329
1330 auth = self.headers.getheader('authorization')
1331 pairs = {}
1332 try:
1333 if not auth:
1334 raise Exception('no auth')
1335 if not auth.startswith('Digest'):
1336 raise Exception('not digest')
1337 # Pull out all the name="value" pairs as a dictionary.
1338 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1339
1340 # Make sure it's all valid.
1341 if pairs['nonce'] != nonce:
1342 raise Exception('wrong nonce')
1343 if pairs['opaque'] != opaque:
1344 raise Exception('wrong opaque')
1345
1346 # Check the 'response' value and make sure it matches our magic hash.
1347 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001348 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001349 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001350 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001351 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001352 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001353 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1354 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001355 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001356
1357 if pairs['response'] != response:
1358 raise Exception('wrong password')
1359 except Exception, e:
1360 # Authentication failed.
1361 self.send_response(401)
1362 hdr = ('Digest '
1363 'realm="%s", '
1364 'domain="/", '
1365 'qop="auth", '
1366 'algorithm=MD5, '
1367 'nonce="%s", '
1368 'opaque="%s"') % (realm, nonce, opaque)
1369 if stale:
1370 hdr += ', stale="TRUE"'
1371 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001372 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001373 self.end_headers()
1374 self.wfile.write('<html><head>')
1375 self.wfile.write('<title>Denied: %s</title>' % e)
1376 self.wfile.write('</head><body>')
1377 self.wfile.write('auth=%s<p>' % auth)
1378 self.wfile.write('pairs=%s<p>' % pairs)
1379 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1380 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1381 self.wfile.write('</body></html>')
1382 return True
1383
1384 # Authentication successful.
1385 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001386 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001387 self.end_headers()
1388 self.wfile.write('<html><head>')
1389 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1390 self.wfile.write('</head><body>')
1391 self.wfile.write('auth=%s<p>' % auth)
1392 self.wfile.write('pairs=%s<p>' % pairs)
1393 self.wfile.write('</body></html>')
1394
1395 return True
1396
1397 def SlowServerHandler(self):
1398 """Wait for the user suggested time before responding. The syntax is
1399 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001400 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001401 return False
1402 query_char = self.path.find('?')
1403 wait_sec = 1.0
1404 if query_char >= 0:
1405 try:
1406 wait_sec = int(self.path[query_char + 1:])
1407 except ValueError:
1408 pass
1409 time.sleep(wait_sec)
1410 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001411 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001412 self.end_headers()
1413 self.wfile.write("waited %d seconds" % wait_sec)
1414 return True
1415
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001416 def ChunkedServerHandler(self):
1417 """Send chunked response. Allows to specify chunks parameters:
1418 - waitBeforeHeaders - ms to wait before sending headers
1419 - waitBetweenChunks - ms to wait between chunks
1420 - chunkSize - size of each chunk in bytes
1421 - chunksNumber - number of chunks
1422 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1423 waits one second, then sends headers and five chunks five bytes each."""
1424 if not self._ShouldHandleRequest("/chunked"):
1425 return False
1426 query_char = self.path.find('?')
1427 chunkedSettings = {'waitBeforeHeaders' : 0,
1428 'waitBetweenChunks' : 0,
1429 'chunkSize' : 5,
1430 'chunksNumber' : 5}
1431 if query_char >= 0:
1432 params = self.path[query_char + 1:].split('&')
1433 for param in params:
1434 keyValue = param.split('=')
1435 if len(keyValue) == 2:
1436 try:
1437 chunkedSettings[keyValue[0]] = int(keyValue[1])
1438 except ValueError:
1439 pass
1440 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1441 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1442 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001443 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001444 self.send_header('Connection', 'close')
1445 self.send_header('Transfer-Encoding', 'chunked')
1446 self.end_headers()
1447 # Chunked encoding: sending all chunks, then final zero-length chunk and
1448 # then final CRLF.
1449 for i in range(0, chunkedSettings['chunksNumber']):
1450 if i > 0:
1451 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1452 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1453 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1454 self.sendChunkHelp('')
1455 return True
1456
initial.commit94958cf2008-07-26 22:42:52 +00001457 def ContentTypeHandler(self):
1458 """Returns a string of html with the given content type. E.g.,
1459 /contenttype?text/css returns an html file with the Content-Type
1460 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001461 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001462 return False
1463 query_char = self.path.find('?')
1464 content_type = self.path[query_char + 1:].strip()
1465 if not content_type:
1466 content_type = 'text/html'
1467 self.send_response(200)
1468 self.send_header('Content-Type', content_type)
1469 self.end_headers()
1470 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1471 return True
1472
creis@google.com2f4f6a42011-03-25 19:44:19 +00001473 def NoContentHandler(self):
1474 """Returns a 204 No Content response."""
1475 if not self._ShouldHandleRequest("/nocontent"):
1476 return False
1477 self.send_response(204)
1478 self.end_headers()
1479 return True
1480
initial.commit94958cf2008-07-26 22:42:52 +00001481 def ServerRedirectHandler(self):
1482 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001483 '/server-redirect?http://foo.bar/asdf' to redirect to
1484 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001485
1486 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001487 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001488 return False
1489
1490 query_char = self.path.find('?')
1491 if query_char < 0 or len(self.path) <= query_char + 1:
1492 self.sendRedirectHelp(test_name)
1493 return True
1494 dest = self.path[query_char + 1:]
1495
1496 self.send_response(301) # moved permanently
1497 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001498 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001499 self.end_headers()
1500 self.wfile.write('<html><head>')
1501 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1502
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001503 return True
initial.commit94958cf2008-07-26 22:42:52 +00001504
1505 def ClientRedirectHandler(self):
1506 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001507 '/client-redirect?http://foo.bar/asdf' to redirect to
1508 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001509
1510 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001511 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001512 return False
1513
1514 query_char = self.path.find('?');
1515 if query_char < 0 or len(self.path) <= query_char + 1:
1516 self.sendRedirectHelp(test_name)
1517 return True
1518 dest = self.path[query_char + 1:]
1519
1520 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001521 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001522 self.end_headers()
1523 self.wfile.write('<html><head>')
1524 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1525 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1526
1527 return True
1528
tony@chromium.org03266982010-03-05 03:18:42 +00001529 def MultipartHandler(self):
1530 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001531 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001532 if not self._ShouldHandleRequest(test_name):
1533 return False
1534
1535 num_frames = 10
1536 bound = '12345'
1537 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001538 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001539 'multipart/x-mixed-replace;boundary=' + bound)
1540 self.end_headers()
1541
1542 for i in xrange(num_frames):
1543 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001544 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001545 self.wfile.write('<title>page ' + str(i) + '</title>')
1546 self.wfile.write('page ' + str(i))
1547
1548 self.wfile.write('--' + bound + '--')
1549 return True
1550
tony@chromium.org4cb88302011-09-27 22:13:49 +00001551 def MultipartSlowHandler(self):
1552 """Send a multipart response (3 text/html pages) with a slight delay
1553 between each page. This is similar to how some pages show status using
1554 multipart."""
1555 test_name = '/multipart-slow'
1556 if not self._ShouldHandleRequest(test_name):
1557 return False
1558
1559 num_frames = 3
1560 bound = '12345'
1561 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001562 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001563 'multipart/x-mixed-replace;boundary=' + bound)
1564 self.end_headers()
1565
1566 for i in xrange(num_frames):
1567 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001568 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001569 time.sleep(0.25)
1570 if i == 2:
1571 self.wfile.write('<title>PASS</title>')
1572 else:
1573 self.wfile.write('<title>page ' + str(i) + '</title>')
1574 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1575
1576 self.wfile.write('--' + bound + '--')
1577 return True
1578
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001579 def GetSSLSessionCacheHandler(self):
1580 """Send a reply containing a log of the session cache operations."""
1581
1582 if not self._ShouldHandleRequest('/ssl-session-cache'):
1583 return False
1584
1585 self.send_response(200)
1586 self.send_header('Content-Type', 'text/plain')
1587 self.end_headers()
1588 try:
1589 for (action, sessionID) in self.server.session_cache.log:
1590 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1591 except AttributeError, e:
1592 self.wfile.write('Pass --https-record-resume in order to use' +
1593 ' this request')
1594 return True
1595
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001596 def CloseSocketHandler(self):
1597 """Closes the socket without sending anything."""
1598
1599 if not self._ShouldHandleRequest('/close-socket'):
1600 return False
1601
1602 self.wfile.close()
1603 return True
1604
initial.commit94958cf2008-07-26 22:42:52 +00001605 def DefaultResponseHandler(self):
1606 """This is the catch-all response handler for requests that aren't handled
1607 by one of the special handlers above.
1608 Note that we specify the content-length as without it the https connection
1609 is not closed properly (and the browser keeps expecting data)."""
1610
1611 contents = "Default response given for path: " + self.path
1612 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001613 self.send_header('Content-Type', 'text/html')
1614 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001615 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001616 if (self.command != 'HEAD'):
1617 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001618 return True
1619
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001620 def RedirectConnectHandler(self):
1621 """Sends a redirect to the CONNECT request for www.redirect.com. This
1622 response is not specified by the RFC, so the browser should not follow
1623 the redirect."""
1624
1625 if (self.path.find("www.redirect.com") < 0):
1626 return False
1627
1628 dest = "http://www.destination.com/foo.js"
1629
1630 self.send_response(302) # moved temporarily
1631 self.send_header('Location', dest)
1632 self.send_header('Connection', 'close')
1633 self.end_headers()
1634 return True
1635
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001636 def ServerAuthConnectHandler(self):
1637 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1638 response doesn't make sense because the proxy server cannot request
1639 server authentication."""
1640
1641 if (self.path.find("www.server-auth.com") < 0):
1642 return False
1643
1644 challenge = 'Basic realm="WallyWorld"'
1645
1646 self.send_response(401) # unauthorized
1647 self.send_header('WWW-Authenticate', challenge)
1648 self.send_header('Connection', 'close')
1649 self.end_headers()
1650 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001651
1652 def DefaultConnectResponseHandler(self):
1653 """This is the catch-all response handler for CONNECT requests that aren't
1654 handled by one of the special handlers above. Real Web servers respond
1655 with 400 to CONNECT requests."""
1656
1657 contents = "Your client has issued a malformed or illegal request."
1658 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001659 self.send_header('Content-Type', 'text/html')
1660 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001661 self.end_headers()
1662 self.wfile.write(contents)
1663 return True
1664
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001665 def DeviceManagementHandler(self):
1666 """Delegates to the device management service used for cloud policy."""
1667 if not self._ShouldHandleRequest("/device_management"):
1668 return False
1669
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001670 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001671
1672 if not self.server._device_management_handler:
1673 import device_management
1674 policy_path = os.path.join(self.server.data_dir, 'device_management')
1675 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001676 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001677 self.server.policy_keys,
1678 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001679
1680 http_response, raw_reply = (
1681 self.server._device_management_handler.HandleRequest(self.path,
1682 self.headers,
1683 raw_request))
1684 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001685 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001686 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001687 self.end_headers()
1688 self.wfile.write(raw_reply)
1689 return True
1690
initial.commit94958cf2008-07-26 22:42:52 +00001691 # called by the redirect handling function when there is no parameter
1692 def sendRedirectHelp(self, redirect_name):
1693 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001694 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001695 self.end_headers()
1696 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1697 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1698 self.wfile.write('</body></html>')
1699
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001700 # called by chunked handling function
1701 def sendChunkHelp(self, chunk):
1702 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1703 self.wfile.write('%X\r\n' % len(chunk))
1704 self.wfile.write(chunk)
1705 self.wfile.write('\r\n')
1706
akalin@chromium.org154bb132010-11-12 02:20:27 +00001707
1708class SyncPageHandler(BasePageHandler):
1709 """Handler for the main HTTP sync server."""
1710
1711 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001712 get_handlers = [self.ChromiumSyncTimeHandler,
1713 self.ChromiumSyncMigrationOpHandler,
1714 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001715 self.ChromiumSyncDisableNotificationsOpHandler,
1716 self.ChromiumSyncEnableNotificationsOpHandler,
1717 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001718 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001719 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001720 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001721 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001722 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001723
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001724 post_handlers = [self.ChromiumSyncCommandHandler,
1725 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001726 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001727 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001728 post_handlers, [])
1729
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001730
akalin@chromium.org154bb132010-11-12 02:20:27 +00001731 def ChromiumSyncTimeHandler(self):
1732 """Handle Chromium sync .../time requests.
1733
1734 The syncer sometimes checks server reachability by examining /time.
1735 """
1736 test_name = "/chromiumsync/time"
1737 if not self._ShouldHandleRequest(test_name):
1738 return False
1739
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001740 # Chrome hates it if we send a response before reading the request.
1741 if self.headers.getheader('content-length'):
1742 length = int(self.headers.getheader('content-length'))
1743 raw_request = self.rfile.read(length)
1744
akalin@chromium.org154bb132010-11-12 02:20:27 +00001745 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001746 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001747 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001748 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001749 return True
1750
1751 def ChromiumSyncCommandHandler(self):
1752 """Handle a chromiumsync command arriving via http.
1753
1754 This covers all sync protocol commands: authentication, getupdates, and
1755 commit.
1756 """
1757 test_name = "/chromiumsync/command"
1758 if not self._ShouldHandleRequest(test_name):
1759 return False
1760
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001761 length = int(self.headers.getheader('content-length'))
1762 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001763 http_response = 200
1764 raw_reply = None
1765 if not self.server.GetAuthenticated():
1766 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001767 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1768 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001769 else:
1770 http_response, raw_reply = self.server.HandleCommand(
1771 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001772
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001773 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001774 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001775 if http_response == 401:
1776 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001777 self.end_headers()
1778 self.wfile.write(raw_reply)
1779 return True
1780
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001781 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001782 test_name = "/chromiumsync/migrate"
1783 if not self._ShouldHandleRequest(test_name):
1784 return False
1785
1786 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1787 self.path)
1788 self.send_response(http_response)
1789 self.send_header('Content-Type', 'text/html')
1790 self.send_header('Content-Length', len(raw_reply))
1791 self.end_headers()
1792 self.wfile.write(raw_reply)
1793 return True
1794
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001795 def ChromiumSyncCredHandler(self):
1796 test_name = "/chromiumsync/cred"
1797 if not self._ShouldHandleRequest(test_name):
1798 return False
1799 try:
1800 query = urlparse.urlparse(self.path)[4]
1801 cred_valid = urlparse.parse_qs(query)['valid']
1802 if cred_valid[0] == 'True':
1803 self.server.SetAuthenticated(True)
1804 else:
1805 self.server.SetAuthenticated(False)
1806 except:
1807 self.server.SetAuthenticated(False)
1808
1809 http_response = 200
1810 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1811 self.send_response(http_response)
1812 self.send_header('Content-Type', 'text/html')
1813 self.send_header('Content-Length', len(raw_reply))
1814 self.end_headers()
1815 self.wfile.write(raw_reply)
1816 return True
1817
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001818 def ChromiumSyncDisableNotificationsOpHandler(self):
1819 test_name = "/chromiumsync/disablenotifications"
1820 if not self._ShouldHandleRequest(test_name):
1821 return False
1822 self.server.GetXmppServer().DisableNotifications()
1823 result = 200
1824 raw_reply = ('<html><title>Notifications disabled</title>'
1825 '<H1>Notifications disabled</H1></html>')
1826 self.send_response(result)
1827 self.send_header('Content-Type', 'text/html')
1828 self.send_header('Content-Length', len(raw_reply))
1829 self.end_headers()
1830 self.wfile.write(raw_reply)
1831 return True;
1832
1833 def ChromiumSyncEnableNotificationsOpHandler(self):
1834 test_name = "/chromiumsync/enablenotifications"
1835 if not self._ShouldHandleRequest(test_name):
1836 return False
1837 self.server.GetXmppServer().EnableNotifications()
1838 result = 200
1839 raw_reply = ('<html><title>Notifications enabled</title>'
1840 '<H1>Notifications enabled</H1></html>')
1841 self.send_response(result)
1842 self.send_header('Content-Type', 'text/html')
1843 self.send_header('Content-Length', len(raw_reply))
1844 self.end_headers()
1845 self.wfile.write(raw_reply)
1846 return True;
1847
1848 def ChromiumSyncSendNotificationOpHandler(self):
1849 test_name = "/chromiumsync/sendnotification"
1850 if not self._ShouldHandleRequest(test_name):
1851 return False
1852 query = urlparse.urlparse(self.path)[4]
1853 query_params = urlparse.parse_qs(query)
1854 channel = ''
1855 data = ''
1856 if 'channel' in query_params:
1857 channel = query_params['channel'][0]
1858 if 'data' in query_params:
1859 data = query_params['data'][0]
1860 self.server.GetXmppServer().SendNotification(channel, data)
1861 result = 200
1862 raw_reply = ('<html><title>Notification sent</title>'
1863 '<H1>Notification sent with channel "%s" '
1864 'and data "%s"</H1></html>'
1865 % (channel, data))
1866 self.send_response(result)
1867 self.send_header('Content-Type', 'text/html')
1868 self.send_header('Content-Length', len(raw_reply))
1869 self.end_headers()
1870 self.wfile.write(raw_reply)
1871 return True;
1872
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001873 def ChromiumSyncBirthdayErrorOpHandler(self):
1874 test_name = "/chromiumsync/birthdayerror"
1875 if not self._ShouldHandleRequest(test_name):
1876 return False
1877 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1878 self.send_response(result)
1879 self.send_header('Content-Type', 'text/html')
1880 self.send_header('Content-Length', len(raw_reply))
1881 self.end_headers()
1882 self.wfile.write(raw_reply)
1883 return True;
1884
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001885 def ChromiumSyncTransientErrorOpHandler(self):
1886 test_name = "/chromiumsync/transienterror"
1887 if not self._ShouldHandleRequest(test_name):
1888 return False
1889 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1890 self.send_response(result)
1891 self.send_header('Content-Type', 'text/html')
1892 self.send_header('Content-Length', len(raw_reply))
1893 self.end_headers()
1894 self.wfile.write(raw_reply)
1895 return True;
1896
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001897 def ChromiumSyncErrorOpHandler(self):
1898 test_name = "/chromiumsync/error"
1899 if not self._ShouldHandleRequest(test_name):
1900 return False
1901 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1902 self.path)
1903 self.send_response(result)
1904 self.send_header('Content-Type', 'text/html')
1905 self.send_header('Content-Length', len(raw_reply))
1906 self.end_headers()
1907 self.wfile.write(raw_reply)
1908 return True;
1909
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001910 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1911 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001912 if not self._ShouldHandleRequest(test_name):
1913 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001914 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001915 self.send_response(result)
1916 self.send_header('Content-Type', 'text/html')
1917 self.send_header('Content-Length', len(raw_reply))
1918 self.end_headers()
1919 self.wfile.write(raw_reply)
1920 return True;
1921
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001922 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1923 test_name = "/chromiumsync/createsyncedbookmarks"
1924 if not self._ShouldHandleRequest(test_name):
1925 return False
1926 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1927 self.send_response(result)
1928 self.send_header('Content-Type', 'text/html')
1929 self.send_header('Content-Length', len(raw_reply))
1930 self.end_headers()
1931 self.wfile.write(raw_reply)
1932 return True;
1933
akalin@chromium.org154bb132010-11-12 02:20:27 +00001934
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001935def MakeDataDir():
1936 if options.data_dir:
1937 if not os.path.isdir(options.data_dir):
1938 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1939 return None
1940 my_data_dir = options.data_dir
1941 else:
1942 # Create the default path to our data dir, relative to the exe dir.
1943 my_data_dir = os.path.dirname(sys.argv[0])
1944 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1945 "test", "data")
1946
1947 #TODO(ibrar): Must use Find* funtion defined in google\tools
1948 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1949
1950 return my_data_dir
1951
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001952class OCSPHandler(BasePageHandler):
1953 def __init__(self, request, client_address, socket_server):
1954 handlers = [self.OCSPResponse]
1955 self.ocsp_response = socket_server.ocsp_response
1956 BasePageHandler.__init__(self, request, client_address, socket_server,
1957 [], handlers, [], handlers, [])
1958
1959 def OCSPResponse(self):
1960 self.send_response(200)
1961 self.send_header('Content-Type', 'application/ocsp-response')
1962 self.send_header('Content-Length', str(len(self.ocsp_response)))
1963 self.end_headers()
1964
1965 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001966
1967class TCPEchoHandler(SocketServer.BaseRequestHandler):
1968 """The RequestHandler class for TCP echo server.
1969
1970 It is instantiated once per connection to the server, and overrides the
1971 handle() method to implement communication to the client.
1972 """
1973
1974 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001975 """Handles the request from the client and constructs a response."""
1976
1977 data = self.request.recv(65536).strip()
1978 # Verify the "echo request" message received from the client. Send back
1979 # "echo response" message if "echo request" message is valid.
1980 try:
1981 return_data = echo_message.GetEchoResponseData(data)
1982 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001983 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001984 except ValueError:
1985 return
1986
1987 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001988
1989
1990class UDPEchoHandler(SocketServer.BaseRequestHandler):
1991 """The RequestHandler class for UDP echo server.
1992
1993 It is instantiated once per connection to the server, and overrides the
1994 handle() method to implement communication to the client.
1995 """
1996
1997 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001998 """Handles the request from the client and constructs a response."""
1999
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002000 data = self.request[0].strip()
2001 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002002 # Verify the "echo request" message received from the client. Send back
2003 # "echo response" message if "echo request" message is valid.
2004 try:
2005 return_data = echo_message.GetEchoResponseData(data)
2006 if not return_data:
2007 return
2008 except ValueError:
2009 return
2010 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002011
2012
bashi@chromium.org33233532012-09-08 17:37:24 +00002013class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2014 """A request handler that behaves as a proxy server which requires
2015 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2016 """
2017
2018 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2019
2020 def parse_request(self):
2021 """Overrides parse_request to check credential."""
2022
2023 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2024 return False
2025
2026 auth = self.headers.getheader('Proxy-Authorization')
2027 if auth != self._AUTH_CREDENTIAL:
2028 self.send_response(407)
2029 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2030 self.end_headers()
2031 return False
2032
2033 return True
2034
2035 def _start_read_write(self, sock):
2036 sock.setblocking(0)
2037 self.request.setblocking(0)
2038 rlist = [self.request, sock]
2039 while True:
2040 ready_sockets, unused, errors = select.select(rlist, [], [])
2041 if errors:
2042 self.send_response(500)
2043 self.end_headers()
2044 return
2045 for s in ready_sockets:
2046 received = s.recv(1024)
2047 if len(received) == 0:
2048 return
2049 if s == self.request:
2050 other = sock
2051 else:
2052 other = self.request
2053 other.send(received)
2054
2055 def _do_common_method(self):
2056 url = urlparse.urlparse(self.path)
2057 port = url.port
2058 if not port:
2059 if url.scheme == 'http':
2060 port = 80
2061 elif url.scheme == 'https':
2062 port = 443
2063 if not url.hostname or not port:
2064 self.send_response(400)
2065 self.end_headers()
2066 return
2067
2068 if len(url.path) == 0:
2069 path = '/'
2070 else:
2071 path = url.path
2072 if len(url.query) > 0:
2073 path = '%s?%s' % (url.path, url.query)
2074
2075 sock = None
2076 try:
2077 sock = socket.create_connection((url.hostname, port))
2078 sock.send('%s %s %s\r\n' % (
2079 self.command, path, self.protocol_version))
2080 for header in self.headers.headers:
2081 header = header.strip()
2082 if (header.lower().startswith('connection') or
2083 header.lower().startswith('proxy')):
2084 continue
2085 sock.send('%s\r\n' % header)
2086 sock.send('\r\n')
2087 self._start_read_write(sock)
2088 except:
2089 self.send_response(500)
2090 self.end_headers()
2091 finally:
2092 if sock is not None:
2093 sock.close()
2094
2095 def do_CONNECT(self):
2096 try:
2097 pos = self.path.rfind(':')
2098 host = self.path[:pos]
2099 port = int(self.path[pos+1:])
2100 except:
2101 self.send_response(400)
2102 self.end_headers()
2103
2104 try:
2105 sock = socket.create_connection((host, port))
2106 self.send_response(200, 'Connection established')
2107 self.end_headers()
2108 self._start_read_write(sock)
2109 except:
2110 self.send_response(500)
2111 self.end_headers()
2112 finally:
2113 sock.close()
2114
2115 def do_GET(self):
2116 self._do_common_method()
2117
2118 def do_HEAD(self):
2119 self._do_common_method()
2120
2121
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002122class FileMultiplexer:
2123 def __init__(self, fd1, fd2) :
2124 self.__fd1 = fd1
2125 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002126
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002127 def __del__(self) :
2128 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2129 self.__fd1.close()
2130 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2131 self.__fd2.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002132
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002133 def write(self, text) :
2134 self.__fd1.write(text)
2135 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002136
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002137 def flush(self) :
2138 self.__fd1.flush()
2139 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002140
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002141def main(options, args):
2142 logfile = open('testserver.log', 'w')
2143 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2144 if options.log_to_console:
2145 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2146 else:
2147 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002148
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002149 port = options.port
2150 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002151
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002152 server_data = {}
2153 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002154
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002155 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002156
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002157 if options.server_type == SERVER_HTTP:
2158 if options.https:
2159 pem_cert_and_key = None
2160 if options.cert_and_key_file:
2161 if not os.path.isfile(options.cert_and_key_file):
2162 print ('specified server cert file not found: ' +
2163 options.cert_and_key_file + ' exiting...')
2164 return
2165 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002166 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002167 # generate a new certificate and run an OCSP server for it.
2168 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2169 print ('OCSP server started on %s:%d...' %
2170 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002171
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002172 ocsp_der = None
2173 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002174
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002175 if options.ocsp == 'ok':
2176 ocsp_state = minica.OCSP_STATE_GOOD
2177 elif options.ocsp == 'revoked':
2178 ocsp_state = minica.OCSP_STATE_REVOKED
2179 elif options.ocsp == 'invalid':
2180 ocsp_state = minica.OCSP_STATE_INVALID
2181 elif options.ocsp == 'unauthorized':
2182 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2183 elif options.ocsp == 'unknown':
2184 ocsp_state = minica.OCSP_STATE_UNKNOWN
2185 else:
2186 print 'unknown OCSP status: ' + options.ocsp_status
2187 return
mattm@chromium.org07e28412012-09-05 00:19:41 +00002188
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002189 (pem_cert_and_key, ocsp_der) = \
2190 minica.GenerateCertKeyAndOCSP(
2191 subject = "127.0.0.1",
2192 ocsp_url = ("http://%s:%d/ocsp" %
2193 (host, ocsp_server.server_port)),
2194 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002195
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002196 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002197
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002198 for ca_cert in options.ssl_client_ca:
2199 if not os.path.isfile(ca_cert):
2200 print 'specified trusted client CA file not found: ' + ca_cert + \
2201 ' exiting...'
2202 return
2203 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2204 options.ssl_client_auth, options.ssl_client_ca,
2205 options.ssl_bulk_cipher, options.record_resume,
2206 options.tls_intolerant)
2207 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002208 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002209 server = HTTPServer((host, port), TestPageHandler)
2210 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002211
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002212 server.data_dir = MakeDataDir()
2213 server.file_root_url = options.file_root_url
2214 server_data['port'] = server.server_port
2215 server._device_management_handler = None
2216 server.policy_keys = options.policy_keys
2217 server.policy_user = options.policy_user
2218 server.gdata_auth_token = options.auth_token
2219 elif options.server_type == SERVER_SYNC:
2220 xmpp_port = options.xmpp_port
2221 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2222 print 'Sync HTTP server started on port %d...' % server.server_port
2223 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2224 server_data['port'] = server.server_port
2225 server_data['xmpp_port'] = server.xmpp_port
2226 elif options.server_type == SERVER_TCP_ECHO:
2227 # Used for generating the key (randomly) that encodes the "echo request"
2228 # message.
2229 random.seed()
2230 server = TCPEchoServer((host, port), TCPEchoHandler)
2231 print 'Echo TCP server started on port %d...' % server.server_port
2232 server_data['port'] = server.server_port
2233 elif options.server_type == SERVER_UDP_ECHO:
2234 # Used for generating the key (randomly) that encodes the "echo request"
2235 # message.
2236 random.seed()
2237 server = UDPEchoServer((host, port), UDPEchoHandler)
2238 print 'Echo UDP server started on port %d...' % server.server_port
2239 server_data['port'] = server.server_port
bashi@chromium.org33233532012-09-08 17:37:24 +00002240 elif options.server_type == SERVER_BASIC_AUTH_PROXY:
2241 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2242 print 'BasicAuthProxy server started on port %d...' % server.server_port
2243 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002244 # means FTP Server
2245 else:
2246 my_data_dir = MakeDataDir()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002247
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002248 # Instantiate a dummy authorizer for managing 'virtual' users
2249 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002250
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002251 # Define a new user having full r/w permissions and a read-only
2252 # anonymous user
2253 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002254
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002255 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002256
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002257 # Instantiate FTP handler class
2258 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2259 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002260
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002261 # Define a customized banner (string returned when client connects)
2262 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2263 pyftpdlib.ftpserver.__ver__)
2264
2265 # Instantiate FTP server class and listen to address:port
2266 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2267 server_data['port'] = server.socket.getsockname()[1]
2268 print 'FTP server started on port %d...' % server_data['port']
2269
2270 # Notify the parent that we've started. (BaseServer subclasses
2271 # bind their sockets on construction.)
2272 if options.startup_pipe is not None:
2273 server_data_json = json.dumps(server_data)
2274 server_data_len = len(server_data_json)
2275 print 'sending server_data: %s (%d bytes)' % (
2276 server_data_json, server_data_len)
2277 if sys.platform == 'win32':
2278 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2279 else:
2280 fd = options.startup_pipe
2281 startup_pipe = os.fdopen(fd, "w")
2282 # First write the data length as an unsigned 4-byte value. This
2283 # is _not_ using network byte ordering since the other end of the
2284 # pipe is on the same machine.
2285 startup_pipe.write(struct.pack('=L', server_data_len))
2286 startup_pipe.write(server_data_json)
2287 startup_pipe.close()
2288
2289 if ocsp_server is not None:
2290 ocsp_server.serve_forever_on_thread()
2291
2292 try:
2293 server.serve_forever()
2294 except KeyboardInterrupt:
2295 print 'shutting down server'
2296 if ocsp_server is not None:
2297 ocsp_server.stop_serving()
2298 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002299
2300if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002301 option_parser = optparse.OptionParser()
2302 option_parser.add_option("-f", '--ftp', action='store_const',
2303 const=SERVER_FTP, default=SERVER_HTTP,
2304 dest='server_type',
2305 help='start up an FTP server.')
2306 option_parser.add_option('', '--sync', action='store_const',
2307 const=SERVER_SYNC, default=SERVER_HTTP,
2308 dest='server_type',
2309 help='start up a sync server.')
2310 option_parser.add_option('', '--tcp-echo', action='store_const',
2311 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2312 dest='server_type',
2313 help='start up a tcp echo server.')
2314 option_parser.add_option('', '--udp-echo', action='store_const',
2315 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2316 dest='server_type',
2317 help='start up a udp echo server.')
bashi@chromium.org33233532012-09-08 17:37:24 +00002318 option_parser.add_option('', '--basic-auth-proxy', action='store_const',
2319 const=SERVER_BASIC_AUTH_PROXY, default=SERVER_HTTP,
2320 dest='server_type',
2321 help='start up a proxy server which requires basic '
2322 'authentication.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002323 option_parser.add_option('', '--log-to-console', action='store_const',
2324 const=True, default=False,
2325 dest='log_to_console',
2326 help='Enables or disables sys.stdout logging to '
2327 'the console.')
2328 option_parser.add_option('', '--port', default='0', type='int',
2329 help='Port used by the server. If unspecified, the '
2330 'server will listen on an ephemeral port.')
2331 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2332 help='Port used by the XMPP server. If unspecified, '
2333 'the XMPP server will listen on an ephemeral port.')
2334 option_parser.add_option('', '--data-dir', dest='data_dir',
2335 help='Directory from which to read the files.')
2336 option_parser.add_option('', '--https', action='store_true', dest='https',
2337 help='Specify that https should be used.')
2338 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2339 help='specify the path to the file containing the '
2340 'certificate and private key for the server in PEM '
2341 'format')
2342 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2343 help='The type of OCSP response generated for the '
2344 'automatically generated certificate. One of '
2345 '[ok,revoked,invalid]')
2346 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2347 default='0', type='int',
2348 help='If nonzero, certain TLS connections will be'
2349 ' aborted in order to test version fallback. 1'
2350 ' means all TLS versions will be aborted. 2 means'
2351 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2352 ' 1.2 or higher will be aborted.')
2353 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2354 const=True, default=False, action='store_const',
2355 help='Record resumption cache events rather than'
2356 ' resuming as normal. Allows the use of the'
2357 ' /ssl-session-cache request')
2358 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2359 help='Require SSL client auth on every connection.')
2360 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2361 help='Specify that the client certificate request '
2362 'should include the CA named in the subject of '
2363 'the DER-encoded certificate contained in the '
2364 'specified file. This option may appear multiple '
2365 'times, indicating multiple CA names should be '
2366 'sent in the request.')
2367 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2368 help='Specify the bulk encryption algorithm(s)'
2369 'that will be accepted by the SSL server. Valid '
2370 'values are "aes256", "aes128", "3des", "rc4". If '
2371 'omitted, all algorithms will be used. This '
2372 'option may appear multiple times, indicating '
2373 'multiple algorithms should be enabled.');
2374 option_parser.add_option('', '--file-root-url', default='/files/',
2375 help='Specify a root URL for files served.')
2376 option_parser.add_option('', '--startup-pipe', type='int',
2377 dest='startup_pipe',
2378 help='File handle of pipe to parent process')
2379 option_parser.add_option('', '--policy-key', action='append',
2380 dest='policy_keys',
2381 help='Specify a path to a PEM-encoded private key '
2382 'to use for policy signing. May be specified '
2383 'multiple times in order to load multipe keys into '
2384 'the server. If ther server has multiple keys, it '
2385 'will rotate through them in at each request a '
2386 'round-robin fashion. The server will generate a '
2387 'random key if none is specified on the command '
2388 'line.')
2389 option_parser.add_option('', '--policy-user', default='user@example.com',
2390 dest='policy_user',
2391 help='Specify the user name the server should '
2392 'report back to the client as the user owning the '
2393 'token used for making the policy request.')
2394 option_parser.add_option('', '--host', default='127.0.0.1',
2395 dest='host',
2396 help='Hostname or IP upon which the server will '
2397 'listen. Client connections will also only be '
2398 'allowed from this address.')
2399 option_parser.add_option('', '--auth-token', dest='auth_token',
2400 help='Specify the auth token which should be used'
2401 'in the authorization header for GData.')
2402 options, args = option_parser.parse_args()
2403
2404 sys.exit(main(options, args))