blob: 1cae65d226624f2de1d2d84f96feb35afa860013 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00006"""This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00007
8It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +00009By default, it listens on an ephemeral port and sends the port number back to
10the originating process over a pipe. The originating process can specify an
11explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000012It can use https if you specify the flag --https=CERT where CERT is the path
13to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000014"""
15
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000016import asyncore
initial.commit94958cf2008-07-26 22:42:52 +000017import base64
18import BaseHTTPServer
19import cgi
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000020import errno
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000021import hashlib
satorux@chromium.orgfdc70122012-03-07 18:08:41 +000022import httplib
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000023import json
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000024import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000025import minica
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000026import optparse
initial.commit94958cf2008-07-26 22:42:52 +000027import os
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000028import random
initial.commit94958cf2008-07-26 22:42:52 +000029import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000030import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000031import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import SocketServer
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000033import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000034import sys
35import threading
initial.commit94958cf2008-07-26 22:42:52 +000036import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000037import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000038import urlparse
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000039import warnings
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000040import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000041
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000042# Ignore deprecation warnings, they make our output more cluttered.
43warnings.filterwarnings("ignore", category=DeprecationWarning)
44
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000045import echo_message
toyoshim@chromium.org716d0572012-10-16 13:52:05 +000046
47# TODO(toyoshim): Some try bots for pyauto don't check out pywebsocket repos
48# unexpectedly. pyauto doesn't use WebSocket module, so just ignore
49# ImportError as a temporal workaround.
50# http://crbug.com/155918
51try:
52 from mod_pywebsocket.standalone import WebSocketServer
53except ImportError:
54 pass
55
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000056import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000057import tlslite
58import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000059
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000060if sys.platform == 'win32':
61 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000062
maruel@chromium.org756cf982009-03-05 12:46:38 +000063SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000064SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000065SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000066SERVER_TCP_ECHO = 3
67SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000068SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000069SERVER_WEBSOCKET = 6
70
71# Default request queue size for WebSocketServer.
72_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000073
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000074# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000075debug_output = sys.stderr
76def debug(str):
77 debug_output.write(str + "\n")
78 debug_output.flush()
79
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000080class WebSocketOptions:
81 """Holds options for WebSocketServer."""
82
83 def __init__(self, host, port, data_dir):
84 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
85 self.server_host = host
86 self.port = port
87 self.websock_handlers = data_dir
88 self.scan_dir = None
89 self.allow_handlers_outside_root_dir = False
90 self.websock_handlers_map_file = None
91 self.cgi_directories = []
92 self.is_executable_method = None
93 self.allow_draft75 = False
94 self.strict = True
95
96 # TODO(toyoshim): Support SSL and authenticates (http://crbug.com/137639)
97 self.use_tls = False
98 self.private_key = None
99 self.certificate = None
100 self.tls_client_ca = None
101 self.use_basic_auth = False
102
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000103class RecordingSSLSessionCache(object):
104 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
105 lookups and inserts in order to test session cache behaviours."""
106
107 def __init__(self):
108 self.log = []
109
110 def __getitem__(self, sessionID):
111 self.log.append(('lookup', sessionID))
112 raise KeyError()
113
114 def __setitem__(self, sessionID, session):
115 self.log.append(('insert', sessionID))
116
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000117
118class ClientRestrictingServerMixIn:
119 """Implements verify_request to limit connections to our configured IP
120 address."""
121
122 def verify_request(self, request, client_address):
123 return client_address[0] == self.server_address[0]
124
125
initial.commit94958cf2008-07-26 22:42:52 +0000126class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000127 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000128 to be exited cleanly (by setting its "stop" member to True)."""
129
130 def serve_forever(self):
131 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000132 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000133 while not self.stop:
134 self.handle_request()
135 self.socket.close()
136
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000137
138class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000139 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000140 verification."""
141
142 pass
143
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000144class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
145 """This is a specialization of HTTPServer that serves an
146 OCSP response"""
147
148 def serve_forever_on_thread(self):
149 self.thread = threading.Thread(target = self.serve_forever,
150 name = "OCSPServerThread")
151 self.thread.start()
152
153 def stop_serving(self):
154 self.shutdown()
155 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000156
157class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
158 ClientRestrictingServerMixIn,
159 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000160 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000161 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000162
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000163 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000164 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000165 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000166 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
167 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000168 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000169 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000170 self.tls_intolerant = tls_intolerant
171
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000172 for ca_file in ssl_client_cas:
173 s = open(ca_file).read()
174 x509 = tlslite.api.X509()
175 x509.parse(s)
176 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000177 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
178 if ssl_bulk_ciphers is not None:
179 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000180
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000181 if record_resume_info:
182 # If record_resume_info is true then we'll replace the session cache with
183 # an object that records the lookups and inserts that it sees.
184 self.session_cache = RecordingSSLSessionCache()
185 else:
186 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000187 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
188
189 def handshake(self, tlsConnection):
190 """Creates the SSL connection."""
191 try:
192 tlsConnection.handshakeServer(certChain=self.cert_chain,
193 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000194 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000195 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000196 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000197 reqCAs=self.ssl_client_cas,
198 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000199 tlsConnection.ignoreAbruptClose = True
200 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000201 except tlslite.api.TLSAbruptCloseError:
202 # Ignore abrupt close.
203 return True
initial.commit94958cf2008-07-26 22:42:52 +0000204 except tlslite.api.TLSError, error:
205 print "Handshake failure:", str(error)
206 return False
207
akalin@chromium.org154bb132010-11-12 02:20:27 +0000208
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000209class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000210 """An HTTP server that handles sync commands."""
211
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000212 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000213 # We import here to avoid pulling in chromiumsync's dependencies
214 # unless strictly necessary.
215 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000216 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000217 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000218 self._sync_handler = chromiumsync.TestServer()
219 self._xmpp_socket_map = {}
220 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000221 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000222 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000223 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000224
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000225 def GetXmppServer(self):
226 return self._xmpp_server
227
akalin@chromium.org154bb132010-11-12 02:20:27 +0000228 def HandleCommand(self, query, raw_request):
229 return self._sync_handler.HandleCommand(query, raw_request)
230
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000231 def HandleRequestNoBlock(self):
232 """Handles a single request.
233
234 Copied from SocketServer._handle_request_noblock().
235 """
236 try:
237 request, client_address = self.get_request()
238 except socket.error:
239 return
240 if self.verify_request(request, client_address):
241 try:
242 self.process_request(request, client_address)
243 except:
244 self.handle_error(request, client_address)
245 self.close_request(request)
246
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000247 def SetAuthenticated(self, auth_valid):
248 self.authenticated = auth_valid
249
250 def GetAuthenticated(self):
251 return self.authenticated
252
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000253 def serve_forever(self):
254 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
255 """
256
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000257 def HandleXmppSocket(fd, socket_map, handler):
258 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000259
260 Adapted from asyncore.read() et al.
261 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000262 xmpp_connection = socket_map.get(fd)
263 # This could happen if a previous handler call caused fd to get
264 # removed from socket_map.
265 if xmpp_connection is None:
266 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000267 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000268 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000269 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
270 raise
271 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000272 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000273
274 while True:
275 read_fds = [ self.fileno() ]
276 write_fds = []
277 exceptional_fds = []
278
279 for fd, xmpp_connection in self._xmpp_socket_map.items():
280 is_r = xmpp_connection.readable()
281 is_w = xmpp_connection.writable()
282 if is_r:
283 read_fds.append(fd)
284 if is_w:
285 write_fds.append(fd)
286 if is_r or is_w:
287 exceptional_fds.append(fd)
288
289 try:
290 read_fds, write_fds, exceptional_fds = (
291 select.select(read_fds, write_fds, exceptional_fds))
292 except select.error, err:
293 if err.args[0] != errno.EINTR:
294 raise
295 else:
296 continue
297
298 for fd in read_fds:
299 if fd == self.fileno():
300 self.HandleRequestNoBlock()
301 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000302 HandleXmppSocket(fd, self._xmpp_socket_map,
303 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000304
305 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000306 HandleXmppSocket(fd, self._xmpp_socket_map,
307 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000308
309 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000310 HandleXmppSocket(fd, self._xmpp_socket_map,
311 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000312
akalin@chromium.org154bb132010-11-12 02:20:27 +0000313
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000314class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
315 """This is a specialization of FTPServer that adds client verification."""
316
317 pass
318
319
320class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000321 """A TCP echo server that echoes back what it has received."""
322
323 def server_bind(self):
324 """Override server_bind to store the server name."""
325 SocketServer.TCPServer.server_bind(self)
326 host, port = self.socket.getsockname()[:2]
327 self.server_name = socket.getfqdn(host)
328 self.server_port = port
329
330 def serve_forever(self):
331 self.stop = False
332 self.nonce_time = None
333 while not self.stop:
334 self.handle_request()
335 self.socket.close()
336
337
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000338class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000339 """A UDP echo server that echoes back what it has received."""
340
341 def server_bind(self):
342 """Override server_bind to store the server name."""
343 SocketServer.UDPServer.server_bind(self)
344 host, port = self.socket.getsockname()[:2]
345 self.server_name = socket.getfqdn(host)
346 self.server_port = port
347
348 def serve_forever(self):
349 self.stop = False
350 self.nonce_time = None
351 while not self.stop:
352 self.handle_request()
353 self.socket.close()
354
355
akalin@chromium.org154bb132010-11-12 02:20:27 +0000356class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
357
358 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000359 connect_handlers, get_handlers, head_handlers, post_handlers,
360 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000361 self._connect_handlers = connect_handlers
362 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000363 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000364 self._post_handlers = post_handlers
365 self._put_handlers = put_handlers
366 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
367 self, request, client_address, socket_server)
368
369 def log_request(self, *args, **kwargs):
370 # Disable request logging to declutter test log output.
371 pass
372
373 def _ShouldHandleRequest(self, handler_name):
374 """Determines if the path can be handled by the handler.
375
376 We consider a handler valid if the path begins with the
377 handler name. It can optionally be followed by "?*", "/*".
378 """
379
380 pattern = re.compile('%s($|\?|/).*' % handler_name)
381 return pattern.match(self.path)
382
383 def do_CONNECT(self):
384 for handler in self._connect_handlers:
385 if handler():
386 return
387
388 def do_GET(self):
389 for handler in self._get_handlers:
390 if handler():
391 return
392
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000393 def do_HEAD(self):
394 for handler in self._head_handlers:
395 if handler():
396 return
397
akalin@chromium.org154bb132010-11-12 02:20:27 +0000398 def do_POST(self):
399 for handler in self._post_handlers:
400 if handler():
401 return
402
403 def do_PUT(self):
404 for handler in self._put_handlers:
405 if handler():
406 return
407
408
409class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000410
411 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000412 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000413 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000414 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000415 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000416 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000417 self.NoCacheMaxAgeTimeHandler,
418 self.NoCacheTimeHandler,
419 self.CacheTimeHandler,
420 self.CacheExpiresHandler,
421 self.CacheProxyRevalidateHandler,
422 self.CachePrivateHandler,
423 self.CachePublicHandler,
424 self.CacheSMaxAgeHandler,
425 self.CacheMustRevalidateHandler,
426 self.CacheMustRevalidateMaxAgeHandler,
427 self.CacheNoStoreHandler,
428 self.CacheNoStoreMaxAgeHandler,
429 self.CacheNoTransformHandler,
430 self.DownloadHandler,
431 self.DownloadFinishHandler,
432 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000433 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000434 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000435 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000436 self.GDataAuthHandler,
437 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000438 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000439 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000440 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000441 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000442 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000443 self.AuthBasicHandler,
444 self.AuthDigestHandler,
445 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000446 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000447 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000448 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000449 self.ServerRedirectHandler,
450 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000451 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000452 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000453 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000454 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000455 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000456 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000457 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000458 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000459 self.DeviceManagementHandler,
460 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000461 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000462 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000463 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000464 head_handlers = [
465 self.FileHandler,
466 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000467
maruel@google.come250a9b2009-03-10 17:39:46 +0000468 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000469 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000470 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000471 'gif': 'image/gif',
472 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000473 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000474 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000475 'pdf' : 'application/pdf',
476 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 }
initial.commit94958cf2008-07-26 22:42:52 +0000478 self._default_mime_type = 'text/html'
479
akalin@chromium.org154bb132010-11-12 02:20:27 +0000480 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000481 connect_handlers, get_handlers, head_handlers,
482 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000483
initial.commit94958cf2008-07-26 22:42:52 +0000484 def GetMIMETypeFromName(self, file_name):
485 """Returns the mime type for the specified file_name. So far it only looks
486 at the file extension."""
487
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000488 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000489 if len(extension) == 0:
490 # no extension.
491 return self._default_mime_type
492
ericroman@google.comc17ca532009-05-07 03:51:05 +0000493 # extension starts with a dot, so we need to remove it
494 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000495
initial.commit94958cf2008-07-26 22:42:52 +0000496 def NoCacheMaxAgeTimeHandler(self):
497 """This request handler yields a page with the title set to the current
498 system time, and no caching requested."""
499
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000500 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000501 return False
502
503 self.send_response(200)
504 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000505 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000506 self.end_headers()
507
maruel@google.come250a9b2009-03-10 17:39:46 +0000508 self.wfile.write('<html><head><title>%s</title></head></html>' %
509 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000510
511 return True
512
513 def NoCacheTimeHandler(self):
514 """This request handler yields a page with the title set to the current
515 system time, and no caching requested."""
516
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000517 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000518 return False
519
520 self.send_response(200)
521 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000522 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000523 self.end_headers()
524
maruel@google.come250a9b2009-03-10 17:39:46 +0000525 self.wfile.write('<html><head><title>%s</title></head></html>' %
526 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000527
528 return True
529
530 def CacheTimeHandler(self):
531 """This request handler yields a page with the title set to the current
532 system time, and allows caching for one minute."""
533
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000534 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000535 return False
536
537 self.send_response(200)
538 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000539 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000540 self.end_headers()
541
maruel@google.come250a9b2009-03-10 17:39:46 +0000542 self.wfile.write('<html><head><title>%s</title></head></html>' %
543 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000544
545 return True
546
547 def CacheExpiresHandler(self):
548 """This request handler yields a page with the title set to the current
549 system time, and set the page to expire on 1 Jan 2099."""
550
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000551 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000552 return False
553
554 self.send_response(200)
555 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000556 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000557 self.end_headers()
558
maruel@google.come250a9b2009-03-10 17:39:46 +0000559 self.wfile.write('<html><head><title>%s</title></head></html>' %
560 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000561
562 return True
563
564 def CacheProxyRevalidateHandler(self):
565 """This request handler yields a page with the title set to the current
566 system time, and allows caching for 60 seconds"""
567
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000568 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000569 return False
570
571 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000572 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000573 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
574 self.end_headers()
575
maruel@google.come250a9b2009-03-10 17:39:46 +0000576 self.wfile.write('<html><head><title>%s</title></head></html>' %
577 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000578
579 return True
580
581 def CachePrivateHandler(self):
582 """This request handler yields a page with the title set to the current
583 system time, and allows caching for 5 seconds."""
584
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000585 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000586 return False
587
588 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000589 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000590 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000591 self.end_headers()
592
maruel@google.come250a9b2009-03-10 17:39:46 +0000593 self.wfile.write('<html><head><title>%s</title></head></html>' %
594 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000595
596 return True
597
598 def CachePublicHandler(self):
599 """This request handler yields a page with the title set to the current
600 system time, and allows caching for 5 seconds."""
601
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000602 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000603 return False
604
605 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000606 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000607 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000608 self.end_headers()
609
maruel@google.come250a9b2009-03-10 17:39:46 +0000610 self.wfile.write('<html><head><title>%s</title></head></html>' %
611 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000612
613 return True
614
615 def CacheSMaxAgeHandler(self):
616 """This request handler yields a page with the title set to the current
617 system time, and does not allow for caching."""
618
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000619 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000620 return False
621
622 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000623 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000624 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
625 self.end_headers()
626
maruel@google.come250a9b2009-03-10 17:39:46 +0000627 self.wfile.write('<html><head><title>%s</title></head></html>' %
628 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000629
630 return True
631
632 def CacheMustRevalidateHandler(self):
633 """This request handler yields a page with the title set to the current
634 system time, and does not allow caching."""
635
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000636 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000637 return False
638
639 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000640 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000641 self.send_header('Cache-Control', 'must-revalidate')
642 self.end_headers()
643
maruel@google.come250a9b2009-03-10 17:39:46 +0000644 self.wfile.write('<html><head><title>%s</title></head></html>' %
645 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000646
647 return True
648
649 def CacheMustRevalidateMaxAgeHandler(self):
650 """This request handler yields a page with the title set to the current
651 system time, and does not allow caching event though max-age of 60
652 seconds is specified."""
653
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000654 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000655 return False
656
657 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000658 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000659 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
660 self.end_headers()
661
maruel@google.come250a9b2009-03-10 17:39:46 +0000662 self.wfile.write('<html><head><title>%s</title></head></html>' %
663 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000664
665 return True
666
initial.commit94958cf2008-07-26 22:42:52 +0000667 def CacheNoStoreHandler(self):
668 """This request handler yields a page with the title set to the current
669 system time, and does not allow the page to be stored."""
670
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000671 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000672 return False
673
674 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000675 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000676 self.send_header('Cache-Control', 'no-store')
677 self.end_headers()
678
maruel@google.come250a9b2009-03-10 17:39:46 +0000679 self.wfile.write('<html><head><title>%s</title></head></html>' %
680 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000681
682 return True
683
684 def CacheNoStoreMaxAgeHandler(self):
685 """This request handler yields a page with the title set to the current
686 system time, and does not allow the page to be stored even though max-age
687 of 60 seconds is specified."""
688
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000689 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000690 return False
691
692 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000693 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000694 self.send_header('Cache-Control', 'max-age=60, no-store')
695 self.end_headers()
696
maruel@google.come250a9b2009-03-10 17:39:46 +0000697 self.wfile.write('<html><head><title>%s</title></head></html>' %
698 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000699
700 return True
701
702
703 def CacheNoTransformHandler(self):
704 """This request handler yields a page with the title set to the current
705 system time, and does not allow the content to transformed during
706 user-agent caching"""
707
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000708 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000709 return False
710
711 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000712 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000713 self.send_header('Cache-Control', 'no-transform')
714 self.end_headers()
715
maruel@google.come250a9b2009-03-10 17:39:46 +0000716 self.wfile.write('<html><head><title>%s</title></head></html>' %
717 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000718
719 return True
720
721 def EchoHeader(self):
722 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000723 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000724
ananta@chromium.org56812d02011-04-07 17:52:05 +0000725 """This function echoes back the value of a specific request header"""
726 """while allowing caching for 16 hours."""
727 def EchoHeaderCache(self):
728 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000729
730 def EchoHeaderHelper(self, echo_header):
731 """This function echoes back the value of the request header passed in."""
732 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000733 return False
734
735 query_char = self.path.find('?')
736 if query_char != -1:
737 header_name = self.path[query_char+1:]
738
739 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000740 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000741 if echo_header == '/echoheadercache':
742 self.send_header('Cache-control', 'max-age=60000')
743 else:
744 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000745 # insert a vary header to properly indicate that the cachability of this
746 # request is subject to value of the request header being echoed.
747 if len(header_name) > 0:
748 self.send_header('Vary', header_name)
749 self.end_headers()
750
751 if len(header_name) > 0:
752 self.wfile.write(self.headers.getheader(header_name))
753
754 return True
755
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000756 def ReadRequestBody(self):
757 """This function reads the body of the current HTTP request, handling
758 both plain and chunked transfer encoded requests."""
759
760 if self.headers.getheader('transfer-encoding') != 'chunked':
761 length = int(self.headers.getheader('content-length'))
762 return self.rfile.read(length)
763
764 # Read the request body as chunks.
765 body = ""
766 while True:
767 line = self.rfile.readline()
768 length = int(line, 16)
769 if length == 0:
770 self.rfile.readline()
771 break
772 body += self.rfile.read(length)
773 self.rfile.read(2)
774 return body
775
initial.commit94958cf2008-07-26 22:42:52 +0000776 def EchoHandler(self):
777 """This handler just echoes back the payload of the request, for testing
778 form submission."""
779
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000780 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000781 return False
782
783 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000784 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000785 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000786 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000787 return True
788
789 def EchoTitleHandler(self):
790 """This handler is like Echo, but sets the page title to the request."""
791
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000792 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000793 return False
794
795 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000796 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000797 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000798 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000799 self.wfile.write('<html><head><title>')
800 self.wfile.write(request)
801 self.wfile.write('</title></head></html>')
802 return True
803
804 def EchoAllHandler(self):
805 """This handler yields a (more) human-readable page listing information
806 about the request header & contents."""
807
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000808 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000809 return False
810
811 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000812 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000813 self.end_headers()
814 self.wfile.write('<html><head><style>'
815 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
816 '</style></head><body>'
817 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000818 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000819 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000820
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000821 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000822 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000823 params = cgi.parse_qs(qs, keep_blank_values=1)
824
825 for param in params:
826 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000827
828 self.wfile.write('</pre>')
829
830 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
831
832 self.wfile.write('</body></html>')
833 return True
834
835 def DownloadHandler(self):
836 """This handler sends a downloadable file with or without reporting
837 the size (6K)."""
838
839 if self.path.startswith("/download-unknown-size"):
840 send_length = False
841 elif self.path.startswith("/download-known-size"):
842 send_length = True
843 else:
844 return False
845
846 #
847 # The test which uses this functionality is attempting to send
848 # small chunks of data to the client. Use a fairly large buffer
849 # so that we'll fill chrome's IO buffer enough to force it to
850 # actually write the data.
851 # See also the comments in the client-side of this test in
852 # download_uitest.cc
853 #
854 size_chunk1 = 35*1024
855 size_chunk2 = 10*1024
856
857 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000858 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000859 self.send_header('Cache-Control', 'max-age=0')
860 if send_length:
861 self.send_header('Content-Length', size_chunk1 + size_chunk2)
862 self.end_headers()
863
864 # First chunk of data:
865 self.wfile.write("*" * size_chunk1)
866 self.wfile.flush()
867
868 # handle requests until one of them clears this flag.
869 self.server.waitForDownload = True
870 while self.server.waitForDownload:
871 self.server.handle_request()
872
873 # Second chunk of data:
874 self.wfile.write("*" * size_chunk2)
875 return True
876
877 def DownloadFinishHandler(self):
878 """This handler just tells the server to finish the current download."""
879
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000880 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000881 return False
882
883 self.server.waitForDownload = False
884 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000885 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000886 self.send_header('Cache-Control', 'max-age=0')
887 self.end_headers()
888 return True
889
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000890 def _ReplaceFileData(self, data, query_parameters):
891 """Replaces matching substrings in a file.
892
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000893 If the 'replace_text' URL query parameter is present, it is expected to be
894 of the form old_text:new_text, which indicates that any old_text strings in
895 the file are replaced with new_text. Multiple 'replace_text' parameters may
896 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000897
898 If the parameters are not present, |data| is returned.
899 """
900 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000901 replace_text_values = query_dict.get('replace_text', [])
902 for replace_text_value in replace_text_values:
903 replace_text_args = replace_text_value.split(':')
904 if len(replace_text_args) != 2:
905 raise ValueError(
906 'replace_text must be of form old_text:new_text. Actual value: %s' %
907 replace_text_value)
908 old_text_b64, new_text_b64 = replace_text_args
909 old_text = base64.urlsafe_b64decode(old_text_b64)
910 new_text = base64.urlsafe_b64decode(new_text_b64)
911 data = data.replace(old_text, new_text)
912 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000913
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000914 def ZipFileHandler(self):
915 """This handler sends the contents of the requested file in compressed form.
916 Can pass in a parameter that specifies that the content length be
917 C - the compressed size (OK),
918 U - the uncompressed size (Non-standard, but handled),
919 S - less than compressed (OK because we keep going),
920 M - larger than compressed but less than uncompressed (an error),
921 L - larger than uncompressed (an error)
922 Example: compressedfiles/Picture_1.doc?C
923 """
924
925 prefix = "/compressedfiles/"
926 if not self.path.startswith(prefix):
927 return False
928
929 # Consume a request body if present.
930 if self.command == 'POST' or self.command == 'PUT' :
931 self.ReadRequestBody()
932
933 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
934
935 if not query in ('C', 'U', 'S', 'M', 'L'):
936 return False
937
938 sub_path = url_path[len(prefix):]
939 entries = sub_path.split('/')
940 file_path = os.path.join(self.server.data_dir, *entries)
941 if os.path.isdir(file_path):
942 file_path = os.path.join(file_path, 'index.html')
943
944 if not os.path.isfile(file_path):
945 print "File not found " + sub_path + " full path:" + file_path
946 self.send_error(404)
947 return True
948
949 f = open(file_path, "rb")
950 data = f.read()
951 uncompressed_len = len(data)
952 f.close()
953
954 # Compress the data.
955 data = zlib.compress(data)
956 compressed_len = len(data)
957
958 content_length = compressed_len
959 if query == 'U':
960 content_length = uncompressed_len
961 elif query == 'S':
962 content_length = compressed_len / 2
963 elif query == 'M':
964 content_length = (compressed_len + uncompressed_len) / 2
965 elif query == 'L':
966 content_length = compressed_len + uncompressed_len
967
968 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000969 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000970 self.send_header('Content-encoding', 'deflate')
971 self.send_header('Connection', 'close')
972 self.send_header('Content-Length', content_length)
973 self.send_header('ETag', '\'' + file_path + '\'')
974 self.end_headers()
975
976 self.wfile.write(data)
977
978 return True
979
initial.commit94958cf2008-07-26 22:42:52 +0000980 def FileHandler(self):
981 """This handler sends the contents of the requested file. Wow, it's like
982 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000983 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000984 if not self.path.startswith(prefix):
985 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000986 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000987
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000988 def PostOnlyFileHandler(self):
989 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000990 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000991 if not self.path.startswith(prefix):
992 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000993 return self._FileHandlerHelper(prefix)
994
995 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000996 request_body = ''
997 if self.command == 'POST' or self.command == 'PUT':
998 # Consume a request body if present.
999 request_body = self.ReadRequestBody()
1000
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001001 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001002 query_dict = cgi.parse_qs(query)
1003
1004 expected_body = query_dict.get('expected_body', [])
1005 if expected_body and request_body not in expected_body:
1006 self.send_response(404)
1007 self.end_headers()
1008 self.wfile.write('')
1009 return True
1010
1011 expected_headers = query_dict.get('expected_headers', [])
1012 for expected_header in expected_headers:
1013 header_name, expected_value = expected_header.split(':')
1014 if self.headers.getheader(header_name) != expected_value:
1015 self.send_response(404)
1016 self.end_headers()
1017 self.wfile.write('')
1018 return True
1019
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001020 sub_path = url_path[len(prefix):]
1021 entries = sub_path.split('/')
1022 file_path = os.path.join(self.server.data_dir, *entries)
1023 if os.path.isdir(file_path):
1024 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001025
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001026 if not os.path.isfile(file_path):
1027 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001028 self.send_error(404)
1029 return True
1030
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001031 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001032 data = f.read()
1033 f.close()
1034
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001035 data = self._ReplaceFileData(data, query)
1036
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001037 old_protocol_version = self.protocol_version
1038
initial.commit94958cf2008-07-26 22:42:52 +00001039 # If file.mock-http-headers exists, it contains the headers we
1040 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001041 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001042 if os.path.isfile(headers_path):
1043 f = open(headers_path, "r")
1044
1045 # "HTTP/1.1 200 OK"
1046 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001047 http_major, http_minor, status_code = re.findall(
1048 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1049 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001050 self.send_response(int(status_code))
1051
1052 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001053 header_values = re.findall('(\S+):\s*(.*)', line)
1054 if len(header_values) > 0:
1055 # "name: value"
1056 name, value = header_values[0]
1057 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001058 f.close()
1059 else:
1060 # Could be more generic once we support mime-type sniffing, but for
1061 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001062
1063 range = self.headers.get('Range')
1064 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001065 # Note this doesn't handle all valid byte range values (i.e. left
1066 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001067 range = range[6:].split('-')
1068 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001069 if range[1]:
1070 end = int(range[1])
1071 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001072 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001073
1074 self.send_response(206)
1075 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1076 str(len(data))
1077 self.send_header('Content-Range', content_range)
1078 data = data[start: end + 1]
1079 else:
1080 self.send_response(200)
1081
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001082 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001083 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001084 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001085 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001086 self.end_headers()
1087
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001088 if (self.command != 'HEAD'):
1089 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001090
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001091 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001092 return True
1093
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001094 def SetCookieHandler(self):
1095 """This handler just sets a cookie, for testing cookie handling."""
1096
1097 if not self._ShouldHandleRequest("/set-cookie"):
1098 return False
1099
1100 query_char = self.path.find('?')
1101 if query_char != -1:
1102 cookie_values = self.path[query_char + 1:].split('&')
1103 else:
1104 cookie_values = ("",)
1105 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001106 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001107 for cookie_value in cookie_values:
1108 self.send_header('Set-Cookie', '%s' % cookie_value)
1109 self.end_headers()
1110 for cookie_value in cookie_values:
1111 self.wfile.write('%s' % cookie_value)
1112 return True
1113
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001114 def SetManyCookiesHandler(self):
1115 """This handler just sets a given number of cookies, for testing handling
1116 of large numbers of cookies."""
1117
1118 if not self._ShouldHandleRequest("/set-many-cookies"):
1119 return False
1120
1121 query_char = self.path.find('?')
1122 if query_char != -1:
1123 num_cookies = int(self.path[query_char + 1:])
1124 else:
1125 num_cookies = 0
1126 self.send_response(200)
1127 self.send_header('', 'text/html')
1128 for i in range(0, num_cookies):
1129 self.send_header('Set-Cookie', 'a=')
1130 self.end_headers()
1131 self.wfile.write('%d cookies were sent' % num_cookies)
1132 return True
1133
mattm@chromium.org983fc462012-06-30 00:52:08 +00001134 def ExpectAndSetCookieHandler(self):
1135 """Expects some cookies to be sent, and if they are, sets more cookies.
1136
1137 The expect parameter specifies a required cookie. May be specified multiple
1138 times.
1139 The set parameter specifies a cookie to set if all required cookies are
1140 preset. May be specified multiple times.
1141 The data parameter specifies the response body data to be returned."""
1142
1143 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1144 return False
1145
1146 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1147 query_dict = cgi.parse_qs(query)
1148 cookies = set()
1149 if 'Cookie' in self.headers:
1150 cookie_header = self.headers.getheader('Cookie')
1151 cookies.update([s.strip() for s in cookie_header.split(';')])
1152 got_all_expected_cookies = True
1153 for expected_cookie in query_dict.get('expect', []):
1154 if expected_cookie not in cookies:
1155 got_all_expected_cookies = False
1156 self.send_response(200)
1157 self.send_header('Content-Type', 'text/html')
1158 if got_all_expected_cookies:
1159 for cookie_value in query_dict.get('set', []):
1160 self.send_header('Set-Cookie', '%s' % cookie_value)
1161 self.end_headers()
1162 for data_value in query_dict.get('data', []):
1163 self.wfile.write(data_value)
1164 return True
1165
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001166 def SetHeaderHandler(self):
1167 """This handler sets a response header. Parameters are in the
1168 key%3A%20value&key2%3A%20value2 format."""
1169
1170 if not self._ShouldHandleRequest("/set-header"):
1171 return False
1172
1173 query_char = self.path.find('?')
1174 if query_char != -1:
1175 headers_values = self.path[query_char + 1:].split('&')
1176 else:
1177 headers_values = ("",)
1178 self.send_response(200)
1179 self.send_header('Content-Type', 'text/html')
1180 for header_value in headers_values:
1181 header_value = urllib.unquote(header_value)
1182 (key, value) = header_value.split(': ', 1)
1183 self.send_header(key, value)
1184 self.end_headers()
1185 for header_value in headers_values:
1186 self.wfile.write('%s' % header_value)
1187 return True
1188
initial.commit94958cf2008-07-26 22:42:52 +00001189 def AuthBasicHandler(self):
1190 """This handler tests 'Basic' authentication. It just sends a page with
1191 title 'user/pass' if you succeed."""
1192
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001193 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001194 return False
1195
1196 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001197 expected_password = 'secret'
1198 realm = 'testrealm'
1199 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001200
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001201 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1202 query_params = cgi.parse_qs(query, True)
1203 if 'set-cookie-if-challenged' in query_params:
1204 set_cookie_if_challenged = True
1205 if 'password' in query_params:
1206 expected_password = query_params['password'][0]
1207 if 'realm' in query_params:
1208 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001209
initial.commit94958cf2008-07-26 22:42:52 +00001210 auth = self.headers.getheader('authorization')
1211 try:
1212 if not auth:
1213 raise Exception('no auth')
1214 b64str = re.findall(r'Basic (\S+)', auth)[0]
1215 userpass = base64.b64decode(b64str)
1216 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001217 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001218 raise Exception('wrong password')
1219 except Exception, e:
1220 # Authentication failed.
1221 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001222 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001223 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001224 if set_cookie_if_challenged:
1225 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001226 self.end_headers()
1227 self.wfile.write('<html><head>')
1228 self.wfile.write('<title>Denied: %s</title>' % e)
1229 self.wfile.write('</head><body>')
1230 self.wfile.write('auth=%s<p>' % auth)
1231 self.wfile.write('b64str=%s<p>' % b64str)
1232 self.wfile.write('username: %s<p>' % username)
1233 self.wfile.write('userpass: %s<p>' % userpass)
1234 self.wfile.write('password: %s<p>' % password)
1235 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1236 self.wfile.write('</body></html>')
1237 return True
1238
1239 # Authentication successful. (Return a cachable response to allow for
1240 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001241 old_protocol_version = self.protocol_version
1242 self.protocol_version = "HTTP/1.1"
1243
initial.commit94958cf2008-07-26 22:42:52 +00001244 if_none_match = self.headers.getheader('if-none-match')
1245 if if_none_match == "abc":
1246 self.send_response(304)
1247 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001248 elif url_path.endswith(".gif"):
1249 # Using chrome/test/data/google/logo.gif as the test image
1250 test_image_path = ['google', 'logo.gif']
1251 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1252 if not os.path.isfile(gif_path):
1253 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001254 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001255 return True
1256
1257 f = open(gif_path, "rb")
1258 data = f.read()
1259 f.close()
1260
1261 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001262 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001263 self.send_header('Cache-control', 'max-age=60000')
1264 self.send_header('Etag', 'abc')
1265 self.end_headers()
1266 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001267 else:
1268 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001269 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001270 self.send_header('Cache-control', 'max-age=60000')
1271 self.send_header('Etag', 'abc')
1272 self.end_headers()
1273 self.wfile.write('<html><head>')
1274 self.wfile.write('<title>%s/%s</title>' % (username, password))
1275 self.wfile.write('</head><body>')
1276 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001277 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001278 self.wfile.write('</body></html>')
1279
rvargas@google.com54453b72011-05-19 01:11:11 +00001280 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001281 return True
1282
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001283 def GDataAuthHandler(self):
1284 """This handler verifies the Authentication header for GData requests."""
1285 if not self.server.gdata_auth_token:
1286 # --auth-token is not specified, not the test case for GData.
1287 return False
1288
1289 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1290 return False
1291
1292 if 'GData-Version' not in self.headers:
1293 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1294 return True
1295
1296 if 'Authorization' not in self.headers:
1297 self.send_error(httplib.UNAUTHORIZED)
1298 return True
1299
1300 field_prefix = 'Bearer '
1301 authorization = self.headers['Authorization']
1302 if not authorization.startswith(field_prefix):
1303 self.send_error(httplib.UNAUTHORIZED)
1304 return True
1305
1306 code = authorization[len(field_prefix):]
1307 if code != self.server.gdata_auth_token:
1308 self.send_error(httplib.UNAUTHORIZED)
1309 return True
1310
1311 return False
1312
1313 def GDataDocumentsFeedQueryHandler(self):
1314 """This handler verifies if required parameters are properly
1315 specified for the GData DocumentsFeed request."""
1316 if not self.server.gdata_auth_token:
1317 # --auth-token is not specified, not the test case for GData.
1318 return False
1319
1320 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1321 return False
1322
1323 (path, question, query_params) = self.path.partition('?')
1324 self.query_params = urlparse.parse_qs(query_params)
1325
1326 if 'v' not in self.query_params:
1327 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1328 return True
1329 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1330 # currently our GData client only uses JSON format.
1331 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1332 return True
1333
1334 return False
1335
tonyg@chromium.org75054202010-03-31 22:06:10 +00001336 def GetNonce(self, force_reset=False):
1337 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001338
tonyg@chromium.org75054202010-03-31 22:06:10 +00001339 This is a fake implementation. A real implementation would only use a given
1340 nonce a single time (hence the name n-once). However, for the purposes of
1341 unittesting, we don't care about the security of the nonce.
1342
1343 Args:
1344 force_reset: Iff set, the nonce will be changed. Useful for testing the
1345 "stale" response.
1346 """
1347 if force_reset or not self.server.nonce_time:
1348 self.server.nonce_time = time.time()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001349 return hashlib.md5('privatekey%s%d' %
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001350 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001351
1352 def AuthDigestHandler(self):
1353 """This handler tests 'Digest' authentication.
1354
1355 It just sends a page with title 'user/pass' if you succeed.
1356
1357 A stale response is sent iff "stale" is present in the request path.
1358 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001359 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001360 return False
1361
tonyg@chromium.org75054202010-03-31 22:06:10 +00001362 stale = 'stale' in self.path
1363 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001364 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001365 password = 'secret'
1366 realm = 'testrealm'
1367
1368 auth = self.headers.getheader('authorization')
1369 pairs = {}
1370 try:
1371 if not auth:
1372 raise Exception('no auth')
1373 if not auth.startswith('Digest'):
1374 raise Exception('not digest')
1375 # Pull out all the name="value" pairs as a dictionary.
1376 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1377
1378 # Make sure it's all valid.
1379 if pairs['nonce'] != nonce:
1380 raise Exception('wrong nonce')
1381 if pairs['opaque'] != opaque:
1382 raise Exception('wrong opaque')
1383
1384 # Check the 'response' value and make sure it matches our magic hash.
1385 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001386 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001387 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001388 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001389 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001390 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001391 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1392 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001393 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001394
1395 if pairs['response'] != response:
1396 raise Exception('wrong password')
1397 except Exception, e:
1398 # Authentication failed.
1399 self.send_response(401)
1400 hdr = ('Digest '
1401 'realm="%s", '
1402 'domain="/", '
1403 'qop="auth", '
1404 'algorithm=MD5, '
1405 'nonce="%s", '
1406 'opaque="%s"') % (realm, nonce, opaque)
1407 if stale:
1408 hdr += ', stale="TRUE"'
1409 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001410 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001411 self.end_headers()
1412 self.wfile.write('<html><head>')
1413 self.wfile.write('<title>Denied: %s</title>' % e)
1414 self.wfile.write('</head><body>')
1415 self.wfile.write('auth=%s<p>' % auth)
1416 self.wfile.write('pairs=%s<p>' % pairs)
1417 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1418 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1419 self.wfile.write('</body></html>')
1420 return True
1421
1422 # Authentication successful.
1423 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001424 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001425 self.end_headers()
1426 self.wfile.write('<html><head>')
1427 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1428 self.wfile.write('</head><body>')
1429 self.wfile.write('auth=%s<p>' % auth)
1430 self.wfile.write('pairs=%s<p>' % pairs)
1431 self.wfile.write('</body></html>')
1432
1433 return True
1434
1435 def SlowServerHandler(self):
1436 """Wait for the user suggested time before responding. The syntax is
1437 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001438 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001439 return False
1440 query_char = self.path.find('?')
1441 wait_sec = 1.0
1442 if query_char >= 0:
1443 try:
1444 wait_sec = int(self.path[query_char + 1:])
1445 except ValueError:
1446 pass
1447 time.sleep(wait_sec)
1448 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001449 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001450 self.end_headers()
1451 self.wfile.write("waited %d seconds" % wait_sec)
1452 return True
1453
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001454 def ChunkedServerHandler(self):
1455 """Send chunked response. Allows to specify chunks parameters:
1456 - waitBeforeHeaders - ms to wait before sending headers
1457 - waitBetweenChunks - ms to wait between chunks
1458 - chunkSize - size of each chunk in bytes
1459 - chunksNumber - number of chunks
1460 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1461 waits one second, then sends headers and five chunks five bytes each."""
1462 if not self._ShouldHandleRequest("/chunked"):
1463 return False
1464 query_char = self.path.find('?')
1465 chunkedSettings = {'waitBeforeHeaders' : 0,
1466 'waitBetweenChunks' : 0,
1467 'chunkSize' : 5,
1468 'chunksNumber' : 5}
1469 if query_char >= 0:
1470 params = self.path[query_char + 1:].split('&')
1471 for param in params:
1472 keyValue = param.split('=')
1473 if len(keyValue) == 2:
1474 try:
1475 chunkedSettings[keyValue[0]] = int(keyValue[1])
1476 except ValueError:
1477 pass
1478 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1479 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1480 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001481 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001482 self.send_header('Connection', 'close')
1483 self.send_header('Transfer-Encoding', 'chunked')
1484 self.end_headers()
1485 # Chunked encoding: sending all chunks, then final zero-length chunk and
1486 # then final CRLF.
1487 for i in range(0, chunkedSettings['chunksNumber']):
1488 if i > 0:
1489 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1490 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1491 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1492 self.sendChunkHelp('')
1493 return True
1494
initial.commit94958cf2008-07-26 22:42:52 +00001495 def ContentTypeHandler(self):
1496 """Returns a string of html with the given content type. E.g.,
1497 /contenttype?text/css returns an html file with the Content-Type
1498 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001499 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001500 return False
1501 query_char = self.path.find('?')
1502 content_type = self.path[query_char + 1:].strip()
1503 if not content_type:
1504 content_type = 'text/html'
1505 self.send_response(200)
1506 self.send_header('Content-Type', content_type)
1507 self.end_headers()
1508 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1509 return True
1510
creis@google.com2f4f6a42011-03-25 19:44:19 +00001511 def NoContentHandler(self):
1512 """Returns a 204 No Content response."""
1513 if not self._ShouldHandleRequest("/nocontent"):
1514 return False
1515 self.send_response(204)
1516 self.end_headers()
1517 return True
1518
initial.commit94958cf2008-07-26 22:42:52 +00001519 def ServerRedirectHandler(self):
1520 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001521 '/server-redirect?http://foo.bar/asdf' to redirect to
1522 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001523
1524 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001525 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001526 return False
1527
1528 query_char = self.path.find('?')
1529 if query_char < 0 or len(self.path) <= query_char + 1:
1530 self.sendRedirectHelp(test_name)
1531 return True
1532 dest = self.path[query_char + 1:]
1533
1534 self.send_response(301) # moved permanently
1535 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001536 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001537 self.end_headers()
1538 self.wfile.write('<html><head>')
1539 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1540
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001541 return True
initial.commit94958cf2008-07-26 22:42:52 +00001542
1543 def ClientRedirectHandler(self):
1544 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001545 '/client-redirect?http://foo.bar/asdf' to redirect to
1546 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001547
1548 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001549 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001550 return False
1551
1552 query_char = self.path.find('?');
1553 if query_char < 0 or len(self.path) <= query_char + 1:
1554 self.sendRedirectHelp(test_name)
1555 return True
1556 dest = self.path[query_char + 1:]
1557
1558 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001559 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001560 self.end_headers()
1561 self.wfile.write('<html><head>')
1562 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1563 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1564
1565 return True
1566
tony@chromium.org03266982010-03-05 03:18:42 +00001567 def MultipartHandler(self):
1568 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001569 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001570 if not self._ShouldHandleRequest(test_name):
1571 return False
1572
1573 num_frames = 10
1574 bound = '12345'
1575 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001576 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001577 'multipart/x-mixed-replace;boundary=' + bound)
1578 self.end_headers()
1579
1580 for i in xrange(num_frames):
1581 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001582 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001583 self.wfile.write('<title>page ' + str(i) + '</title>')
1584 self.wfile.write('page ' + str(i))
1585
1586 self.wfile.write('--' + bound + '--')
1587 return True
1588
tony@chromium.org4cb88302011-09-27 22:13:49 +00001589 def MultipartSlowHandler(self):
1590 """Send a multipart response (3 text/html pages) with a slight delay
1591 between each page. This is similar to how some pages show status using
1592 multipart."""
1593 test_name = '/multipart-slow'
1594 if not self._ShouldHandleRequest(test_name):
1595 return False
1596
1597 num_frames = 3
1598 bound = '12345'
1599 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001600 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001601 'multipart/x-mixed-replace;boundary=' + bound)
1602 self.end_headers()
1603
1604 for i in xrange(num_frames):
1605 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001606 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001607 time.sleep(0.25)
1608 if i == 2:
1609 self.wfile.write('<title>PASS</title>')
1610 else:
1611 self.wfile.write('<title>page ' + str(i) + '</title>')
1612 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1613
1614 self.wfile.write('--' + bound + '--')
1615 return True
1616
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001617 def GetSSLSessionCacheHandler(self):
1618 """Send a reply containing a log of the session cache operations."""
1619
1620 if not self._ShouldHandleRequest('/ssl-session-cache'):
1621 return False
1622
1623 self.send_response(200)
1624 self.send_header('Content-Type', 'text/plain')
1625 self.end_headers()
1626 try:
1627 for (action, sessionID) in self.server.session_cache.log:
1628 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1629 except AttributeError, e:
1630 self.wfile.write('Pass --https-record-resume in order to use' +
1631 ' this request')
1632 return True
1633
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001634 def CloseSocketHandler(self):
1635 """Closes the socket without sending anything."""
1636
1637 if not self._ShouldHandleRequest('/close-socket'):
1638 return False
1639
1640 self.wfile.close()
1641 return True
1642
initial.commit94958cf2008-07-26 22:42:52 +00001643 def DefaultResponseHandler(self):
1644 """This is the catch-all response handler for requests that aren't handled
1645 by one of the special handlers above.
1646 Note that we specify the content-length as without it the https connection
1647 is not closed properly (and the browser keeps expecting data)."""
1648
1649 contents = "Default response given for path: " + self.path
1650 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001651 self.send_header('Content-Type', 'text/html')
1652 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001653 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001654 if (self.command != 'HEAD'):
1655 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001656 return True
1657
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001658 def RedirectConnectHandler(self):
1659 """Sends a redirect to the CONNECT request for www.redirect.com. This
1660 response is not specified by the RFC, so the browser should not follow
1661 the redirect."""
1662
1663 if (self.path.find("www.redirect.com") < 0):
1664 return False
1665
1666 dest = "http://www.destination.com/foo.js"
1667
1668 self.send_response(302) # moved temporarily
1669 self.send_header('Location', dest)
1670 self.send_header('Connection', 'close')
1671 self.end_headers()
1672 return True
1673
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001674 def ServerAuthConnectHandler(self):
1675 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1676 response doesn't make sense because the proxy server cannot request
1677 server authentication."""
1678
1679 if (self.path.find("www.server-auth.com") < 0):
1680 return False
1681
1682 challenge = 'Basic realm="WallyWorld"'
1683
1684 self.send_response(401) # unauthorized
1685 self.send_header('WWW-Authenticate', challenge)
1686 self.send_header('Connection', 'close')
1687 self.end_headers()
1688 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001689
1690 def DefaultConnectResponseHandler(self):
1691 """This is the catch-all response handler for CONNECT requests that aren't
1692 handled by one of the special handlers above. Real Web servers respond
1693 with 400 to CONNECT requests."""
1694
1695 contents = "Your client has issued a malformed or illegal request."
1696 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001697 self.send_header('Content-Type', 'text/html')
1698 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001699 self.end_headers()
1700 self.wfile.write(contents)
1701 return True
1702
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001703 def DeviceManagementHandler(self):
1704 """Delegates to the device management service used for cloud policy."""
1705 if not self._ShouldHandleRequest("/device_management"):
1706 return False
1707
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001708 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001709
1710 if not self.server._device_management_handler:
1711 import device_management
1712 policy_path = os.path.join(self.server.data_dir, 'device_management')
1713 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001714 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001715 self.server.policy_keys,
1716 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001717
1718 http_response, raw_reply = (
1719 self.server._device_management_handler.HandleRequest(self.path,
1720 self.headers,
1721 raw_request))
1722 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001723 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001724 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001725 self.end_headers()
1726 self.wfile.write(raw_reply)
1727 return True
1728
initial.commit94958cf2008-07-26 22:42:52 +00001729 # called by the redirect handling function when there is no parameter
1730 def sendRedirectHelp(self, redirect_name):
1731 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001732 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001733 self.end_headers()
1734 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1735 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1736 self.wfile.write('</body></html>')
1737
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001738 # called by chunked handling function
1739 def sendChunkHelp(self, chunk):
1740 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1741 self.wfile.write('%X\r\n' % len(chunk))
1742 self.wfile.write(chunk)
1743 self.wfile.write('\r\n')
1744
akalin@chromium.org154bb132010-11-12 02:20:27 +00001745
1746class SyncPageHandler(BasePageHandler):
1747 """Handler for the main HTTP sync server."""
1748
1749 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001750 get_handlers = [self.ChromiumSyncTimeHandler,
1751 self.ChromiumSyncMigrationOpHandler,
1752 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001753 self.ChromiumSyncDisableNotificationsOpHandler,
1754 self.ChromiumSyncEnableNotificationsOpHandler,
1755 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001756 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001757 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001758 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001759 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001760 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001761
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001762 post_handlers = [self.ChromiumSyncCommandHandler,
1763 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001764 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001765 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001766 post_handlers, [])
1767
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001768
akalin@chromium.org154bb132010-11-12 02:20:27 +00001769 def ChromiumSyncTimeHandler(self):
1770 """Handle Chromium sync .../time requests.
1771
1772 The syncer sometimes checks server reachability by examining /time.
1773 """
1774 test_name = "/chromiumsync/time"
1775 if not self._ShouldHandleRequest(test_name):
1776 return False
1777
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001778 # Chrome hates it if we send a response before reading the request.
1779 if self.headers.getheader('content-length'):
1780 length = int(self.headers.getheader('content-length'))
1781 raw_request = self.rfile.read(length)
1782
akalin@chromium.org154bb132010-11-12 02:20:27 +00001783 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001784 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001785 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001786 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001787 return True
1788
1789 def ChromiumSyncCommandHandler(self):
1790 """Handle a chromiumsync command arriving via http.
1791
1792 This covers all sync protocol commands: authentication, getupdates, and
1793 commit.
1794 """
1795 test_name = "/chromiumsync/command"
1796 if not self._ShouldHandleRequest(test_name):
1797 return False
1798
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001799 length = int(self.headers.getheader('content-length'))
1800 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001801 http_response = 200
1802 raw_reply = None
1803 if not self.server.GetAuthenticated():
1804 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001805 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1806 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001807 else:
1808 http_response, raw_reply = self.server.HandleCommand(
1809 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001810
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001811 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001812 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001813 if http_response == 401:
1814 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001815 self.end_headers()
1816 self.wfile.write(raw_reply)
1817 return True
1818
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001819 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001820 test_name = "/chromiumsync/migrate"
1821 if not self._ShouldHandleRequest(test_name):
1822 return False
1823
1824 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1825 self.path)
1826 self.send_response(http_response)
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
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001833 def ChromiumSyncCredHandler(self):
1834 test_name = "/chromiumsync/cred"
1835 if not self._ShouldHandleRequest(test_name):
1836 return False
1837 try:
1838 query = urlparse.urlparse(self.path)[4]
1839 cred_valid = urlparse.parse_qs(query)['valid']
1840 if cred_valid[0] == 'True':
1841 self.server.SetAuthenticated(True)
1842 else:
1843 self.server.SetAuthenticated(False)
1844 except:
1845 self.server.SetAuthenticated(False)
1846
1847 http_response = 200
1848 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1849 self.send_response(http_response)
1850 self.send_header('Content-Type', 'text/html')
1851 self.send_header('Content-Length', len(raw_reply))
1852 self.end_headers()
1853 self.wfile.write(raw_reply)
1854 return True
1855
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001856 def ChromiumSyncDisableNotificationsOpHandler(self):
1857 test_name = "/chromiumsync/disablenotifications"
1858 if not self._ShouldHandleRequest(test_name):
1859 return False
1860 self.server.GetXmppServer().DisableNotifications()
1861 result = 200
1862 raw_reply = ('<html><title>Notifications disabled</title>'
1863 '<H1>Notifications disabled</H1></html>')
1864 self.send_response(result)
1865 self.send_header('Content-Type', 'text/html')
1866 self.send_header('Content-Length', len(raw_reply))
1867 self.end_headers()
1868 self.wfile.write(raw_reply)
1869 return True;
1870
1871 def ChromiumSyncEnableNotificationsOpHandler(self):
1872 test_name = "/chromiumsync/enablenotifications"
1873 if not self._ShouldHandleRequest(test_name):
1874 return False
1875 self.server.GetXmppServer().EnableNotifications()
1876 result = 200
1877 raw_reply = ('<html><title>Notifications enabled</title>'
1878 '<H1>Notifications enabled</H1></html>')
1879 self.send_response(result)
1880 self.send_header('Content-Type', 'text/html')
1881 self.send_header('Content-Length', len(raw_reply))
1882 self.end_headers()
1883 self.wfile.write(raw_reply)
1884 return True;
1885
1886 def ChromiumSyncSendNotificationOpHandler(self):
1887 test_name = "/chromiumsync/sendnotification"
1888 if not self._ShouldHandleRequest(test_name):
1889 return False
1890 query = urlparse.urlparse(self.path)[4]
1891 query_params = urlparse.parse_qs(query)
1892 channel = ''
1893 data = ''
1894 if 'channel' in query_params:
1895 channel = query_params['channel'][0]
1896 if 'data' in query_params:
1897 data = query_params['data'][0]
1898 self.server.GetXmppServer().SendNotification(channel, data)
1899 result = 200
1900 raw_reply = ('<html><title>Notification sent</title>'
1901 '<H1>Notification sent with channel "%s" '
1902 'and data "%s"</H1></html>'
1903 % (channel, data))
1904 self.send_response(result)
1905 self.send_header('Content-Type', 'text/html')
1906 self.send_header('Content-Length', len(raw_reply))
1907 self.end_headers()
1908 self.wfile.write(raw_reply)
1909 return True;
1910
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001911 def ChromiumSyncBirthdayErrorOpHandler(self):
1912 test_name = "/chromiumsync/birthdayerror"
1913 if not self._ShouldHandleRequest(test_name):
1914 return False
1915 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1916 self.send_response(result)
1917 self.send_header('Content-Type', 'text/html')
1918 self.send_header('Content-Length', len(raw_reply))
1919 self.end_headers()
1920 self.wfile.write(raw_reply)
1921 return True;
1922
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001923 def ChromiumSyncTransientErrorOpHandler(self):
1924 test_name = "/chromiumsync/transienterror"
1925 if not self._ShouldHandleRequest(test_name):
1926 return False
1927 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1928 self.send_response(result)
1929 self.send_header('Content-Type', 'text/html')
1930 self.send_header('Content-Length', len(raw_reply))
1931 self.end_headers()
1932 self.wfile.write(raw_reply)
1933 return True;
1934
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001935 def ChromiumSyncErrorOpHandler(self):
1936 test_name = "/chromiumsync/error"
1937 if not self._ShouldHandleRequest(test_name):
1938 return False
1939 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1940 self.path)
1941 self.send_response(result)
1942 self.send_header('Content-Type', 'text/html')
1943 self.send_header('Content-Length', len(raw_reply))
1944 self.end_headers()
1945 self.wfile.write(raw_reply)
1946 return True;
1947
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001948 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1949 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001950 if not self._ShouldHandleRequest(test_name):
1951 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001952 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001953 self.send_response(result)
1954 self.send_header('Content-Type', 'text/html')
1955 self.send_header('Content-Length', len(raw_reply))
1956 self.end_headers()
1957 self.wfile.write(raw_reply)
1958 return True;
1959
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001960 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1961 test_name = "/chromiumsync/createsyncedbookmarks"
1962 if not self._ShouldHandleRequest(test_name):
1963 return False
1964 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1965 self.send_response(result)
1966 self.send_header('Content-Type', 'text/html')
1967 self.send_header('Content-Length', len(raw_reply))
1968 self.end_headers()
1969 self.wfile.write(raw_reply)
1970 return True;
1971
akalin@chromium.org154bb132010-11-12 02:20:27 +00001972
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001973def MakeDataDir():
1974 if options.data_dir:
1975 if not os.path.isdir(options.data_dir):
1976 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1977 return None
1978 my_data_dir = options.data_dir
1979 else:
1980 # Create the default path to our data dir, relative to the exe dir.
1981 my_data_dir = os.path.dirname(sys.argv[0])
1982 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1983 "test", "data")
1984
1985 #TODO(ibrar): Must use Find* funtion defined in google\tools
1986 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1987
1988 return my_data_dir
1989
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001990class OCSPHandler(BasePageHandler):
1991 def __init__(self, request, client_address, socket_server):
1992 handlers = [self.OCSPResponse]
1993 self.ocsp_response = socket_server.ocsp_response
1994 BasePageHandler.__init__(self, request, client_address, socket_server,
1995 [], handlers, [], handlers, [])
1996
1997 def OCSPResponse(self):
1998 self.send_response(200)
1999 self.send_header('Content-Type', 'application/ocsp-response')
2000 self.send_header('Content-Length', str(len(self.ocsp_response)))
2001 self.end_headers()
2002
2003 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002004
2005class TCPEchoHandler(SocketServer.BaseRequestHandler):
2006 """The RequestHandler class for TCP echo server.
2007
2008 It is instantiated once per connection to the server, and overrides the
2009 handle() method to implement communication to the client.
2010 """
2011
2012 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002013 """Handles the request from the client and constructs a response."""
2014
2015 data = self.request.recv(65536).strip()
2016 # Verify the "echo request" message received from the client. Send back
2017 # "echo response" message if "echo request" message is valid.
2018 try:
2019 return_data = echo_message.GetEchoResponseData(data)
2020 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002021 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002022 except ValueError:
2023 return
2024
2025 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002026
2027
2028class UDPEchoHandler(SocketServer.BaseRequestHandler):
2029 """The RequestHandler class for UDP echo server.
2030
2031 It is instantiated once per connection to the server, and overrides the
2032 handle() method to implement communication to the client.
2033 """
2034
2035 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002036 """Handles the request from the client and constructs a response."""
2037
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002038 data = self.request[0].strip()
2039 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002040 # Verify the "echo request" message received from the client. Send back
2041 # "echo response" message if "echo request" message is valid.
2042 try:
2043 return_data = echo_message.GetEchoResponseData(data)
2044 if not return_data:
2045 return
2046 except ValueError:
2047 return
2048 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002049
2050
bashi@chromium.org33233532012-09-08 17:37:24 +00002051class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2052 """A request handler that behaves as a proxy server which requires
2053 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2054 """
2055
2056 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2057
2058 def parse_request(self):
2059 """Overrides parse_request to check credential."""
2060
2061 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2062 return False
2063
2064 auth = self.headers.getheader('Proxy-Authorization')
2065 if auth != self._AUTH_CREDENTIAL:
2066 self.send_response(407)
2067 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2068 self.end_headers()
2069 return False
2070
2071 return True
2072
2073 def _start_read_write(self, sock):
2074 sock.setblocking(0)
2075 self.request.setblocking(0)
2076 rlist = [self.request, sock]
2077 while True:
2078 ready_sockets, unused, errors = select.select(rlist, [], [])
2079 if errors:
2080 self.send_response(500)
2081 self.end_headers()
2082 return
2083 for s in ready_sockets:
2084 received = s.recv(1024)
2085 if len(received) == 0:
2086 return
2087 if s == self.request:
2088 other = sock
2089 else:
2090 other = self.request
2091 other.send(received)
2092
2093 def _do_common_method(self):
2094 url = urlparse.urlparse(self.path)
2095 port = url.port
2096 if not port:
2097 if url.scheme == 'http':
2098 port = 80
2099 elif url.scheme == 'https':
2100 port = 443
2101 if not url.hostname or not port:
2102 self.send_response(400)
2103 self.end_headers()
2104 return
2105
2106 if len(url.path) == 0:
2107 path = '/'
2108 else:
2109 path = url.path
2110 if len(url.query) > 0:
2111 path = '%s?%s' % (url.path, url.query)
2112
2113 sock = None
2114 try:
2115 sock = socket.create_connection((url.hostname, port))
2116 sock.send('%s %s %s\r\n' % (
2117 self.command, path, self.protocol_version))
2118 for header in self.headers.headers:
2119 header = header.strip()
2120 if (header.lower().startswith('connection') or
2121 header.lower().startswith('proxy')):
2122 continue
2123 sock.send('%s\r\n' % header)
2124 sock.send('\r\n')
2125 self._start_read_write(sock)
2126 except:
2127 self.send_response(500)
2128 self.end_headers()
2129 finally:
2130 if sock is not None:
2131 sock.close()
2132
2133 def do_CONNECT(self):
2134 try:
2135 pos = self.path.rfind(':')
2136 host = self.path[:pos]
2137 port = int(self.path[pos+1:])
2138 except:
2139 self.send_response(400)
2140 self.end_headers()
2141
2142 try:
2143 sock = socket.create_connection((host, port))
2144 self.send_response(200, 'Connection established')
2145 self.end_headers()
2146 self._start_read_write(sock)
2147 except:
2148 self.send_response(500)
2149 self.end_headers()
2150 finally:
2151 sock.close()
2152
2153 def do_GET(self):
2154 self._do_common_method()
2155
2156 def do_HEAD(self):
2157 self._do_common_method()
2158
2159
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002160class FileMultiplexer:
2161 def __init__(self, fd1, fd2) :
2162 self.__fd1 = fd1
2163 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002164
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002165 def __del__(self) :
2166 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2167 self.__fd1.close()
2168 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2169 self.__fd2.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002170
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002171 def write(self, text) :
2172 self.__fd1.write(text)
2173 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002174
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002175 def flush(self) :
2176 self.__fd1.flush()
2177 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002178
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002179def main(options, args):
2180 logfile = open('testserver.log', 'w')
2181 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2182 if options.log_to_console:
2183 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2184 else:
2185 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002186
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002187 port = options.port
2188 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002189
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002190 server_data = {}
2191 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002192
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002193 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002194
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002195 if options.server_type == SERVER_HTTP:
2196 if options.https:
2197 pem_cert_and_key = None
2198 if options.cert_and_key_file:
2199 if not os.path.isfile(options.cert_and_key_file):
2200 print ('specified server cert file not found: ' +
2201 options.cert_and_key_file + ' exiting...')
2202 return
2203 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002204 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002205 # generate a new certificate and run an OCSP server for it.
2206 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2207 print ('OCSP server started on %s:%d...' %
2208 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002209
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002210 ocsp_der = None
2211 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002212
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002213 if options.ocsp == 'ok':
2214 ocsp_state = minica.OCSP_STATE_GOOD
2215 elif options.ocsp == 'revoked':
2216 ocsp_state = minica.OCSP_STATE_REVOKED
2217 elif options.ocsp == 'invalid':
2218 ocsp_state = minica.OCSP_STATE_INVALID
2219 elif options.ocsp == 'unauthorized':
2220 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2221 elif options.ocsp == 'unknown':
2222 ocsp_state = minica.OCSP_STATE_UNKNOWN
2223 else:
2224 print 'unknown OCSP status: ' + options.ocsp_status
2225 return
mattm@chromium.org07e28412012-09-05 00:19:41 +00002226
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002227 (pem_cert_and_key, ocsp_der) = \
2228 minica.GenerateCertKeyAndOCSP(
2229 subject = "127.0.0.1",
2230 ocsp_url = ("http://%s:%d/ocsp" %
2231 (host, ocsp_server.server_port)),
2232 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002233
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002234 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002235
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002236 for ca_cert in options.ssl_client_ca:
2237 if not os.path.isfile(ca_cert):
2238 print 'specified trusted client CA file not found: ' + ca_cert + \
2239 ' exiting...'
2240 return
2241 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2242 options.ssl_client_auth, options.ssl_client_ca,
2243 options.ssl_bulk_cipher, options.record_resume,
2244 options.tls_intolerant)
2245 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002246 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002247 server = HTTPServer((host, port), TestPageHandler)
2248 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002249
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002250 server.data_dir = MakeDataDir()
2251 server.file_root_url = options.file_root_url
2252 server_data['port'] = server.server_port
2253 server._device_management_handler = None
2254 server.policy_keys = options.policy_keys
2255 server.policy_user = options.policy_user
2256 server.gdata_auth_token = options.auth_token
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002257 elif options.server_type == SERVER_WEBSOCKET:
2258 # Launch pywebsocket via WebSocketServer.
2259 logger = logging.getLogger()
2260 logger.addHandler(logging.StreamHandler())
2261 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2262 # is required to work correctly. It should be fixed from pywebsocket side.
2263 os.chdir(MakeDataDir())
toyoshim@chromium.org3740bce2012-10-13 20:52:19 +00002264 server = WebSocketServer(WebSocketOptions(host, port, '.'))
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002265 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2266 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002267 elif options.server_type == SERVER_SYNC:
2268 xmpp_port = options.xmpp_port
2269 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2270 print 'Sync HTTP server started on port %d...' % server.server_port
2271 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2272 server_data['port'] = server.server_port
2273 server_data['xmpp_port'] = server.xmpp_port
2274 elif options.server_type == SERVER_TCP_ECHO:
2275 # Used for generating the key (randomly) that encodes the "echo request"
2276 # message.
2277 random.seed()
2278 server = TCPEchoServer((host, port), TCPEchoHandler)
2279 print 'Echo TCP server started on port %d...' % server.server_port
2280 server_data['port'] = server.server_port
2281 elif options.server_type == SERVER_UDP_ECHO:
2282 # Used for generating the key (randomly) that encodes the "echo request"
2283 # message.
2284 random.seed()
2285 server = UDPEchoServer((host, port), UDPEchoHandler)
2286 print 'Echo UDP server started on port %d...' % server.server_port
2287 server_data['port'] = server.server_port
bashi@chromium.org33233532012-09-08 17:37:24 +00002288 elif options.server_type == SERVER_BASIC_AUTH_PROXY:
2289 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2290 print 'BasicAuthProxy server started on port %d...' % server.server_port
2291 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002292 # means FTP Server
2293 else:
2294 my_data_dir = MakeDataDir()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002295
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002296 # Instantiate a dummy authorizer for managing 'virtual' users
2297 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002298
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002299 # Define a new user having full r/w permissions and a read-only
2300 # anonymous user
2301 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002302
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002303 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002304
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002305 # Instantiate FTP handler class
2306 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2307 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002308
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002309 # Define a customized banner (string returned when client connects)
2310 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2311 pyftpdlib.ftpserver.__ver__)
2312
2313 # Instantiate FTP server class and listen to address:port
2314 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2315 server_data['port'] = server.socket.getsockname()[1]
2316 print 'FTP server started on port %d...' % server_data['port']
2317
2318 # Notify the parent that we've started. (BaseServer subclasses
2319 # bind their sockets on construction.)
2320 if options.startup_pipe is not None:
2321 server_data_json = json.dumps(server_data)
2322 server_data_len = len(server_data_json)
2323 print 'sending server_data: %s (%d bytes)' % (
2324 server_data_json, server_data_len)
2325 if sys.platform == 'win32':
2326 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2327 else:
2328 fd = options.startup_pipe
2329 startup_pipe = os.fdopen(fd, "w")
2330 # First write the data length as an unsigned 4-byte value. This
2331 # is _not_ using network byte ordering since the other end of the
2332 # pipe is on the same machine.
2333 startup_pipe.write(struct.pack('=L', server_data_len))
2334 startup_pipe.write(server_data_json)
2335 startup_pipe.close()
2336
2337 if ocsp_server is not None:
2338 ocsp_server.serve_forever_on_thread()
2339
2340 try:
2341 server.serve_forever()
2342 except KeyboardInterrupt:
2343 print 'shutting down server'
2344 if ocsp_server is not None:
2345 ocsp_server.stop_serving()
2346 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002347
2348if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002349 option_parser = optparse.OptionParser()
2350 option_parser.add_option("-f", '--ftp', action='store_const',
2351 const=SERVER_FTP, default=SERVER_HTTP,
2352 dest='server_type',
2353 help='start up an FTP server.')
2354 option_parser.add_option('', '--sync', action='store_const',
2355 const=SERVER_SYNC, default=SERVER_HTTP,
2356 dest='server_type',
2357 help='start up a sync server.')
2358 option_parser.add_option('', '--tcp-echo', action='store_const',
2359 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2360 dest='server_type',
2361 help='start up a tcp echo server.')
2362 option_parser.add_option('', '--udp-echo', action='store_const',
2363 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2364 dest='server_type',
2365 help='start up a udp echo server.')
bashi@chromium.org33233532012-09-08 17:37:24 +00002366 option_parser.add_option('', '--basic-auth-proxy', action='store_const',
2367 const=SERVER_BASIC_AUTH_PROXY, default=SERVER_HTTP,
2368 dest='server_type',
2369 help='start up a proxy server which requires basic '
2370 'authentication.')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002371 option_parser.add_option('', '--websocket', action='store_const',
2372 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2373 dest='server_type',
2374 help='start up a WebSocket server.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002375 option_parser.add_option('', '--log-to-console', action='store_const',
2376 const=True, default=False,
2377 dest='log_to_console',
2378 help='Enables or disables sys.stdout logging to '
2379 'the console.')
2380 option_parser.add_option('', '--port', default='0', type='int',
2381 help='Port used by the server. If unspecified, the '
2382 'server will listen on an ephemeral port.')
2383 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2384 help='Port used by the XMPP server. If unspecified, '
2385 'the XMPP server will listen on an ephemeral port.')
2386 option_parser.add_option('', '--data-dir', dest='data_dir',
2387 help='Directory from which to read the files.')
2388 option_parser.add_option('', '--https', action='store_true', dest='https',
2389 help='Specify that https should be used.')
2390 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2391 help='specify the path to the file containing the '
2392 'certificate and private key for the server in PEM '
2393 'format')
2394 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2395 help='The type of OCSP response generated for the '
2396 'automatically generated certificate. One of '
2397 '[ok,revoked,invalid]')
2398 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2399 default='0', type='int',
2400 help='If nonzero, certain TLS connections will be'
2401 ' aborted in order to test version fallback. 1'
2402 ' means all TLS versions will be aborted. 2 means'
2403 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2404 ' 1.2 or higher will be aborted.')
2405 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2406 const=True, default=False, action='store_const',
2407 help='Record resumption cache events rather than'
2408 ' resuming as normal. Allows the use of the'
2409 ' /ssl-session-cache request')
2410 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2411 help='Require SSL client auth on every connection.')
2412 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2413 help='Specify that the client certificate request '
2414 'should include the CA named in the subject of '
2415 'the DER-encoded certificate contained in the '
2416 'specified file. This option may appear multiple '
2417 'times, indicating multiple CA names should be '
2418 'sent in the request.')
2419 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2420 help='Specify the bulk encryption algorithm(s)'
2421 'that will be accepted by the SSL server. Valid '
2422 'values are "aes256", "aes128", "3des", "rc4". If '
2423 'omitted, all algorithms will be used. This '
2424 'option may appear multiple times, indicating '
2425 'multiple algorithms should be enabled.');
2426 option_parser.add_option('', '--file-root-url', default='/files/',
2427 help='Specify a root URL for files served.')
2428 option_parser.add_option('', '--startup-pipe', type='int',
2429 dest='startup_pipe',
2430 help='File handle of pipe to parent process')
2431 option_parser.add_option('', '--policy-key', action='append',
2432 dest='policy_keys',
2433 help='Specify a path to a PEM-encoded private key '
2434 'to use for policy signing. May be specified '
2435 'multiple times in order to load multipe keys into '
2436 'the server. If ther server has multiple keys, it '
2437 'will rotate through them in at each request a '
2438 'round-robin fashion. The server will generate a '
2439 'random key if none is specified on the command '
2440 'line.')
2441 option_parser.add_option('', '--policy-user', default='user@example.com',
2442 dest='policy_user',
2443 help='Specify the user name the server should '
2444 'report back to the client as the user owning the '
2445 'token used for making the policy request.')
2446 option_parser.add_option('', '--host', default='127.0.0.1',
2447 dest='host',
2448 help='Hostname or IP upon which the server will '
2449 'listen. Client connections will also only be '
2450 'allowed from this address.')
2451 option_parser.add_option('', '--auth-token', dest='auth_token',
2452 help='Specify the auth token which should be used'
2453 'in the authorization header for GData.')
2454 options, args = option_parser.parse_args()
2455
2456 sys.exit(main(options, args))