blob: 55bca1e039acb3fcd857a047a080e69e3f8f9ce8 [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.orgaa1b6e72012-10-09 03:43:19 +000046from mod_pywebsocket.standalone import WebSocketServer
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000047import pyftpdlib.ftpserver
initial.commit94958cf2008-07-26 22:42:52 +000048import tlslite
49import tlslite.api
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000050
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +000051if sys.platform == 'win32':
52 import msvcrt
davidben@chromium.org06fcf202010-09-22 18:15:23 +000053
maruel@chromium.org756cf982009-03-05 12:46:38 +000054SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000055SERVER_FTP = 1
akalin@chromium.org154bb132010-11-12 02:20:27 +000056SERVER_SYNC = 2
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +000057SERVER_TCP_ECHO = 3
58SERVER_UDP_ECHO = 4
bashi@chromium.org33233532012-09-08 17:37:24 +000059SERVER_BASIC_AUTH_PROXY = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000060SERVER_WEBSOCKET = 6
61
62# Default request queue size for WebSocketServer.
63_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000064
akalin@chromium.orgf8479c62010-11-27 01:52:52 +000065# Using debug() seems to cause hangs on XP: see http://crbug.com/64515 .
initial.commit94958cf2008-07-26 22:42:52 +000066debug_output = sys.stderr
67def debug(str):
68 debug_output.write(str + "\n")
69 debug_output.flush()
70
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000071class WebSocketOptions:
72 """Holds options for WebSocketServer."""
73
74 def __init__(self, host, port, data_dir):
75 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
76 self.server_host = host
77 self.port = port
78 self.websock_handlers = data_dir
79 self.scan_dir = None
80 self.allow_handlers_outside_root_dir = False
81 self.websock_handlers_map_file = None
82 self.cgi_directories = []
83 self.is_executable_method = None
84 self.allow_draft75 = False
85 self.strict = True
86
87 # TODO(toyoshim): Support SSL and authenticates (http://crbug.com/137639)
88 self.use_tls = False
89 self.private_key = None
90 self.certificate = None
91 self.tls_client_ca = None
92 self.use_basic_auth = False
93
agl@chromium.orgf9e66792011-12-12 22:22:19 +000094class RecordingSSLSessionCache(object):
95 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
96 lookups and inserts in order to test session cache behaviours."""
97
98 def __init__(self):
99 self.log = []
100
101 def __getitem__(self, sessionID):
102 self.log.append(('lookup', sessionID))
103 raise KeyError()
104
105 def __setitem__(self, sessionID, session):
106 self.log.append(('insert', sessionID))
107
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000108
109class ClientRestrictingServerMixIn:
110 """Implements verify_request to limit connections to our configured IP
111 address."""
112
113 def verify_request(self, request, client_address):
114 return client_address[0] == self.server_address[0]
115
116
initial.commit94958cf2008-07-26 22:42:52 +0000117class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000118 """This is a specialization of BaseHTTPServer to allow it
initial.commit94958cf2008-07-26 22:42:52 +0000119 to be exited cleanly (by setting its "stop" member to True)."""
120
121 def serve_forever(self):
122 self.stop = False
tonyg@chromium.org75054202010-03-31 22:06:10 +0000123 self.nonce_time = None
initial.commit94958cf2008-07-26 22:42:52 +0000124 while not self.stop:
125 self.handle_request()
126 self.socket.close()
127
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000128
129class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000130 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000131 verification."""
132
133 pass
134
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000135class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
136 """This is a specialization of HTTPServer that serves an
137 OCSP response"""
138
139 def serve_forever_on_thread(self):
140 self.thread = threading.Thread(target = self.serve_forever,
141 name = "OCSPServerThread")
142 self.thread.start()
143
144 def stop_serving(self):
145 self.shutdown()
146 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000147
148class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
149 ClientRestrictingServerMixIn,
150 StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000151 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000152 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000153
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000154 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000155 ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
agl@chromium.org143daa42012-04-26 18:45:34 +0000156 record_resume_info, tls_intolerant):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000157 self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
158 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
davidben@chromium.org31282a12010-08-07 01:10:02 +0000159 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000160 self.ssl_client_cas = []
agl@chromium.org143daa42012-04-26 18:45:34 +0000161 self.tls_intolerant = tls_intolerant
162
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000163 for ca_file in ssl_client_cas:
164 s = open(ca_file).read()
165 x509 = tlslite.api.X509()
166 x509.parse(s)
167 self.ssl_client_cas.append(x509.subject)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000168 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
169 if ssl_bulk_ciphers is not None:
170 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
initial.commit94958cf2008-07-26 22:42:52 +0000171
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000172 if record_resume_info:
173 # If record_resume_info is true then we'll replace the session cache with
174 # an object that records the lookups and inserts that it sees.
175 self.session_cache = RecordingSSLSessionCache()
176 else:
177 self.session_cache = tlslite.api.SessionCache()
initial.commit94958cf2008-07-26 22:42:52 +0000178 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
179
180 def handshake(self, tlsConnection):
181 """Creates the SSL connection."""
182 try:
183 tlsConnection.handshakeServer(certChain=self.cert_chain,
184 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000185 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000186 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000187 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000188 reqCAs=self.ssl_client_cas,
189 tlsIntolerant=self.tls_intolerant)
initial.commit94958cf2008-07-26 22:42:52 +0000190 tlsConnection.ignoreAbruptClose = True
191 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000192 except tlslite.api.TLSAbruptCloseError:
193 # Ignore abrupt close.
194 return True
initial.commit94958cf2008-07-26 22:42:52 +0000195 except tlslite.api.TLSError, error:
196 print "Handshake failure:", str(error)
197 return False
198
akalin@chromium.org154bb132010-11-12 02:20:27 +0000199
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000200class SyncHTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000201 """An HTTP server that handles sync commands."""
202
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000203 def __init__(self, server_address, xmpp_port, request_handler_class):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000204 # We import here to avoid pulling in chromiumsync's dependencies
205 # unless strictly necessary.
206 import chromiumsync
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000207 import xmppserver
akalin@chromium.org154bb132010-11-12 02:20:27 +0000208 StoppableHTTPServer.__init__(self, server_address, request_handler_class)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000209 self._sync_handler = chromiumsync.TestServer()
210 self._xmpp_socket_map = {}
211 self._xmpp_server = xmppserver.XmppServer(
rsimha@chromium.org6a5525c2012-05-24 20:40:21 +0000212 self._xmpp_socket_map, ('localhost', xmpp_port))
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000213 self.xmpp_port = self._xmpp_server.getsockname()[1]
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000214 self.authenticated = True
akalin@chromium.org154bb132010-11-12 02:20:27 +0000215
akalin@chromium.org0332ec72011-08-27 06:53:21 +0000216 def GetXmppServer(self):
217 return self._xmpp_server
218
akalin@chromium.org154bb132010-11-12 02:20:27 +0000219 def HandleCommand(self, query, raw_request):
220 return self._sync_handler.HandleCommand(query, raw_request)
221
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000222 def HandleRequestNoBlock(self):
223 """Handles a single request.
224
225 Copied from SocketServer._handle_request_noblock().
226 """
227 try:
228 request, client_address = self.get_request()
229 except socket.error:
230 return
231 if self.verify_request(request, client_address):
232 try:
233 self.process_request(request, client_address)
234 except:
235 self.handle_error(request, client_address)
236 self.close_request(request)
237
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +0000238 def SetAuthenticated(self, auth_valid):
239 self.authenticated = auth_valid
240
241 def GetAuthenticated(self):
242 return self.authenticated
243
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000244 def serve_forever(self):
245 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
246 """
247
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000248 def HandleXmppSocket(fd, socket_map, handler):
249 """Runs the handler for the xmpp connection for fd.
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000250
251 Adapted from asyncore.read() et al.
252 """
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000253 xmpp_connection = socket_map.get(fd)
254 # This could happen if a previous handler call caused fd to get
255 # removed from socket_map.
256 if xmpp_connection is None:
257 return
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000258 try:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000259 handler(xmpp_connection)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000260 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
261 raise
262 except:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000263 xmpp_connection.handle_error()
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000264
265 while True:
266 read_fds = [ self.fileno() ]
267 write_fds = []
268 exceptional_fds = []
269
270 for fd, xmpp_connection in self._xmpp_socket_map.items():
271 is_r = xmpp_connection.readable()
272 is_w = xmpp_connection.writable()
273 if is_r:
274 read_fds.append(fd)
275 if is_w:
276 write_fds.append(fd)
277 if is_r or is_w:
278 exceptional_fds.append(fd)
279
280 try:
281 read_fds, write_fds, exceptional_fds = (
282 select.select(read_fds, write_fds, exceptional_fds))
283 except select.error, err:
284 if err.args[0] != errno.EINTR:
285 raise
286 else:
287 continue
288
289 for fd in read_fds:
290 if fd == self.fileno():
291 self.HandleRequestNoBlock()
292 continue
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000293 HandleXmppSocket(fd, self._xmpp_socket_map,
294 asyncore.dispatcher.handle_read_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000295
296 for fd in write_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000297 HandleXmppSocket(fd, self._xmpp_socket_map,
298 asyncore.dispatcher.handle_write_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000299
300 for fd in exceptional_fds:
akalin@chromium.org3c0f42f2010-12-20 21:23:44 +0000301 HandleXmppSocket(fd, self._xmpp_socket_map,
302 asyncore.dispatcher.handle_expt_event)
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +0000303
akalin@chromium.org154bb132010-11-12 02:20:27 +0000304
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000305class FTPServer(ClientRestrictingServerMixIn, pyftpdlib.ftpserver.FTPServer):
306 """This is a specialization of FTPServer that adds client verification."""
307
308 pass
309
310
311class TCPEchoServer(ClientRestrictingServerMixIn, SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000312 """A TCP echo server that echoes back what it has received."""
313
314 def server_bind(self):
315 """Override server_bind to store the server name."""
316 SocketServer.TCPServer.server_bind(self)
317 host, port = self.socket.getsockname()[:2]
318 self.server_name = socket.getfqdn(host)
319 self.server_port = port
320
321 def serve_forever(self):
322 self.stop = False
323 self.nonce_time = None
324 while not self.stop:
325 self.handle_request()
326 self.socket.close()
327
328
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000329class UDPEchoServer(ClientRestrictingServerMixIn, SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000330 """A UDP echo server that echoes back what it has received."""
331
332 def server_bind(self):
333 """Override server_bind to store the server name."""
334 SocketServer.UDPServer.server_bind(self)
335 host, port = self.socket.getsockname()[:2]
336 self.server_name = socket.getfqdn(host)
337 self.server_port = port
338
339 def serve_forever(self):
340 self.stop = False
341 self.nonce_time = None
342 while not self.stop:
343 self.handle_request()
344 self.socket.close()
345
346
akalin@chromium.org154bb132010-11-12 02:20:27 +0000347class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
348
349 def __init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000350 connect_handlers, get_handlers, head_handlers, post_handlers,
351 put_handlers):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352 self._connect_handlers = connect_handlers
353 self._get_handlers = get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000354 self._head_handlers = head_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000355 self._post_handlers = post_handlers
356 self._put_handlers = put_handlers
357 BaseHTTPServer.BaseHTTPRequestHandler.__init__(
358 self, request, client_address, socket_server)
359
360 def log_request(self, *args, **kwargs):
361 # Disable request logging to declutter test log output.
362 pass
363
364 def _ShouldHandleRequest(self, handler_name):
365 """Determines if the path can be handled by the handler.
366
367 We consider a handler valid if the path begins with the
368 handler name. It can optionally be followed by "?*", "/*".
369 """
370
371 pattern = re.compile('%s($|\?|/).*' % handler_name)
372 return pattern.match(self.path)
373
374 def do_CONNECT(self):
375 for handler in self._connect_handlers:
376 if handler():
377 return
378
379 def do_GET(self):
380 for handler in self._get_handlers:
381 if handler():
382 return
383
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000384 def do_HEAD(self):
385 for handler in self._head_handlers:
386 if handler():
387 return
388
akalin@chromium.org154bb132010-11-12 02:20:27 +0000389 def do_POST(self):
390 for handler in self._post_handlers:
391 if handler():
392 return
393
394 def do_PUT(self):
395 for handler in self._put_handlers:
396 if handler():
397 return
398
399
400class TestPageHandler(BasePageHandler):
initial.commit94958cf2008-07-26 22:42:52 +0000401
402 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000403 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000404 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000405 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000406 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000407 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000408 self.NoCacheMaxAgeTimeHandler,
409 self.NoCacheTimeHandler,
410 self.CacheTimeHandler,
411 self.CacheExpiresHandler,
412 self.CacheProxyRevalidateHandler,
413 self.CachePrivateHandler,
414 self.CachePublicHandler,
415 self.CacheSMaxAgeHandler,
416 self.CacheMustRevalidateHandler,
417 self.CacheMustRevalidateMaxAgeHandler,
418 self.CacheNoStoreHandler,
419 self.CacheNoStoreMaxAgeHandler,
420 self.CacheNoTransformHandler,
421 self.DownloadHandler,
422 self.DownloadFinishHandler,
423 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000424 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000425 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000426 self.ZipFileHandler,
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000427 self.GDataAuthHandler,
428 self.GDataDocumentsFeedQueryHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000429 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000430 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000431 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000432 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000433 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000434 self.AuthBasicHandler,
435 self.AuthDigestHandler,
436 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000437 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000438 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000439 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.ServerRedirectHandler,
441 self.ClientRedirectHandler,
tony@chromium.org03266982010-03-05 03:18:42 +0000442 self.MultipartHandler,
tony@chromium.org4cb88302011-09-27 22:13:49 +0000443 self.MultipartSlowHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000444 self.GetSSLSessionCacheHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000445 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000446 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000447 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000449 self.EchoHandler,
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000450 self.DeviceManagementHandler,
451 self.PostOnlyFileHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000452 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000453 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000454 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000455 head_handlers = [
456 self.FileHandler,
457 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000458
maruel@google.come250a9b2009-03-10 17:39:46 +0000459 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000460 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000461 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000462 'gif': 'image/gif',
463 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000464 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000465 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000466 'pdf' : 'application/pdf',
467 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000468 }
initial.commit94958cf2008-07-26 22:42:52 +0000469 self._default_mime_type = 'text/html'
470
akalin@chromium.org154bb132010-11-12 02:20:27 +0000471 BasePageHandler.__init__(self, request, client_address, socket_server,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000472 connect_handlers, get_handlers, head_handlers,
473 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000474
initial.commit94958cf2008-07-26 22:42:52 +0000475 def GetMIMETypeFromName(self, file_name):
476 """Returns the mime type for the specified file_name. So far it only looks
477 at the file extension."""
478
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000479 (shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000480 if len(extension) == 0:
481 # no extension.
482 return self._default_mime_type
483
ericroman@google.comc17ca532009-05-07 03:51:05 +0000484 # extension starts with a dot, so we need to remove it
485 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000486
initial.commit94958cf2008-07-26 22:42:52 +0000487 def NoCacheMaxAgeTimeHandler(self):
488 """This request handler yields a page with the title set to the current
489 system time, and no caching requested."""
490
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000491 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000492 return False
493
494 self.send_response(200)
495 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000496 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000497 self.end_headers()
498
maruel@google.come250a9b2009-03-10 17:39:46 +0000499 self.wfile.write('<html><head><title>%s</title></head></html>' %
500 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000501
502 return True
503
504 def NoCacheTimeHandler(self):
505 """This request handler yields a page with the title set to the current
506 system time, and no caching requested."""
507
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000508 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000509 return False
510
511 self.send_response(200)
512 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000513 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000514 self.end_headers()
515
maruel@google.come250a9b2009-03-10 17:39:46 +0000516 self.wfile.write('<html><head><title>%s</title></head></html>' %
517 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000518
519 return True
520
521 def CacheTimeHandler(self):
522 """This request handler yields a page with the title set to the current
523 system time, and allows caching for one minute."""
524
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000525 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000526 return False
527
528 self.send_response(200)
529 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000530 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000531 self.end_headers()
532
maruel@google.come250a9b2009-03-10 17:39:46 +0000533 self.wfile.write('<html><head><title>%s</title></head></html>' %
534 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000535
536 return True
537
538 def CacheExpiresHandler(self):
539 """This request handler yields a page with the title set to the current
540 system time, and set the page to expire on 1 Jan 2099."""
541
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000542 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000543 return False
544
545 self.send_response(200)
546 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000547 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000548 self.end_headers()
549
maruel@google.come250a9b2009-03-10 17:39:46 +0000550 self.wfile.write('<html><head><title>%s</title></head></html>' %
551 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000552
553 return True
554
555 def CacheProxyRevalidateHandler(self):
556 """This request handler yields a page with the title set to the current
557 system time, and allows caching for 60 seconds"""
558
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000559 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000560 return False
561
562 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000563 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000564 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
565 self.end_headers()
566
maruel@google.come250a9b2009-03-10 17:39:46 +0000567 self.wfile.write('<html><head><title>%s</title></head></html>' %
568 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000569
570 return True
571
572 def CachePrivateHandler(self):
573 """This request handler yields a page with the title set to the current
574 system time, and allows caching for 5 seconds."""
575
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000576 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000577 return False
578
579 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000580 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000581 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000582 self.end_headers()
583
maruel@google.come250a9b2009-03-10 17:39:46 +0000584 self.wfile.write('<html><head><title>%s</title></head></html>' %
585 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000586
587 return True
588
589 def CachePublicHandler(self):
590 """This request handler yields a page with the title set to the current
591 system time, and allows caching for 5 seconds."""
592
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000593 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000594 return False
595
596 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000597 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000598 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000599 self.end_headers()
600
maruel@google.come250a9b2009-03-10 17:39:46 +0000601 self.wfile.write('<html><head><title>%s</title></head></html>' %
602 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000603
604 return True
605
606 def CacheSMaxAgeHandler(self):
607 """This request handler yields a page with the title set to the current
608 system time, and does not allow for caching."""
609
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000610 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000611 return False
612
613 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000614 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000615 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
616 self.end_headers()
617
maruel@google.come250a9b2009-03-10 17:39:46 +0000618 self.wfile.write('<html><head><title>%s</title></head></html>' %
619 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000620
621 return True
622
623 def CacheMustRevalidateHandler(self):
624 """This request handler yields a page with the title set to the current
625 system time, and does not allow caching."""
626
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000627 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000628 return False
629
630 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000631 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000632 self.send_header('Cache-Control', 'must-revalidate')
633 self.end_headers()
634
maruel@google.come250a9b2009-03-10 17:39:46 +0000635 self.wfile.write('<html><head><title>%s</title></head></html>' %
636 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000637
638 return True
639
640 def CacheMustRevalidateMaxAgeHandler(self):
641 """This request handler yields a page with the title set to the current
642 system time, and does not allow caching event though max-age of 60
643 seconds is specified."""
644
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000645 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000646 return False
647
648 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000649 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000650 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
651 self.end_headers()
652
maruel@google.come250a9b2009-03-10 17:39:46 +0000653 self.wfile.write('<html><head><title>%s</title></head></html>' %
654 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000655
656 return True
657
initial.commit94958cf2008-07-26 22:42:52 +0000658 def CacheNoStoreHandler(self):
659 """This request handler yields a page with the title set to the current
660 system time, and does not allow the page to be stored."""
661
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000662 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000663 return False
664
665 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000666 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000667 self.send_header('Cache-Control', 'no-store')
668 self.end_headers()
669
maruel@google.come250a9b2009-03-10 17:39:46 +0000670 self.wfile.write('<html><head><title>%s</title></head></html>' %
671 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000672
673 return True
674
675 def CacheNoStoreMaxAgeHandler(self):
676 """This request handler yields a page with the title set to the current
677 system time, and does not allow the page to be stored even though max-age
678 of 60 seconds is specified."""
679
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000680 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000681 return False
682
683 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000684 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000685 self.send_header('Cache-Control', 'max-age=60, no-store')
686 self.end_headers()
687
maruel@google.come250a9b2009-03-10 17:39:46 +0000688 self.wfile.write('<html><head><title>%s</title></head></html>' %
689 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000690
691 return True
692
693
694 def CacheNoTransformHandler(self):
695 """This request handler yields a page with the title set to the current
696 system time, and does not allow the content to transformed during
697 user-agent caching"""
698
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000699 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000700 return False
701
702 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000703 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000704 self.send_header('Cache-Control', 'no-transform')
705 self.end_headers()
706
maruel@google.come250a9b2009-03-10 17:39:46 +0000707 self.wfile.write('<html><head><title>%s</title></head></html>' %
708 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000709
710 return True
711
712 def EchoHeader(self):
713 """This handler echoes back the value of a specific request header."""
ananta@chromium.org219b2062009-10-23 16:09:41 +0000714 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000715
ananta@chromium.org56812d02011-04-07 17:52:05 +0000716 """This function echoes back the value of a specific request header"""
717 """while allowing caching for 16 hours."""
718 def EchoHeaderCache(self):
719 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000720
721 def EchoHeaderHelper(self, echo_header):
722 """This function echoes back the value of the request header passed in."""
723 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000724 return False
725
726 query_char = self.path.find('?')
727 if query_char != -1:
728 header_name = self.path[query_char+1:]
729
730 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000731 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000732 if echo_header == '/echoheadercache':
733 self.send_header('Cache-control', 'max-age=60000')
734 else:
735 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000736 # insert a vary header to properly indicate that the cachability of this
737 # request is subject to value of the request header being echoed.
738 if len(header_name) > 0:
739 self.send_header('Vary', header_name)
740 self.end_headers()
741
742 if len(header_name) > 0:
743 self.wfile.write(self.headers.getheader(header_name))
744
745 return True
746
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000747 def ReadRequestBody(self):
748 """This function reads the body of the current HTTP request, handling
749 both plain and chunked transfer encoded requests."""
750
751 if self.headers.getheader('transfer-encoding') != 'chunked':
752 length = int(self.headers.getheader('content-length'))
753 return self.rfile.read(length)
754
755 # Read the request body as chunks.
756 body = ""
757 while True:
758 line = self.rfile.readline()
759 length = int(line, 16)
760 if length == 0:
761 self.rfile.readline()
762 break
763 body += self.rfile.read(length)
764 self.rfile.read(2)
765 return body
766
initial.commit94958cf2008-07-26 22:42:52 +0000767 def EchoHandler(self):
768 """This handler just echoes back the payload of the request, for testing
769 form submission."""
770
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000771 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000772 return False
773
774 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000775 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000776 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000777 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000778 return True
779
780 def EchoTitleHandler(self):
781 """This handler is like Echo, but sets the page title to the request."""
782
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000783 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000784 return False
785
786 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000787 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000788 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000789 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000790 self.wfile.write('<html><head><title>')
791 self.wfile.write(request)
792 self.wfile.write('</title></head></html>')
793 return True
794
795 def EchoAllHandler(self):
796 """This handler yields a (more) human-readable page listing information
797 about the request header & contents."""
798
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000799 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000800 return False
801
802 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000803 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000804 self.end_headers()
805 self.wfile.write('<html><head><style>'
806 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
807 '</style></head><body>'
808 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000809 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000810 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000811
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000812 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000813 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000814 params = cgi.parse_qs(qs, keep_blank_values=1)
815
816 for param in params:
817 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000818
819 self.wfile.write('</pre>')
820
821 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
822
823 self.wfile.write('</body></html>')
824 return True
825
826 def DownloadHandler(self):
827 """This handler sends a downloadable file with or without reporting
828 the size (6K)."""
829
830 if self.path.startswith("/download-unknown-size"):
831 send_length = False
832 elif self.path.startswith("/download-known-size"):
833 send_length = True
834 else:
835 return False
836
837 #
838 # The test which uses this functionality is attempting to send
839 # small chunks of data to the client. Use a fairly large buffer
840 # so that we'll fill chrome's IO buffer enough to force it to
841 # actually write the data.
842 # See also the comments in the client-side of this test in
843 # download_uitest.cc
844 #
845 size_chunk1 = 35*1024
846 size_chunk2 = 10*1024
847
848 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000849 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000850 self.send_header('Cache-Control', 'max-age=0')
851 if send_length:
852 self.send_header('Content-Length', size_chunk1 + size_chunk2)
853 self.end_headers()
854
855 # First chunk of data:
856 self.wfile.write("*" * size_chunk1)
857 self.wfile.flush()
858
859 # handle requests until one of them clears this flag.
860 self.server.waitForDownload = True
861 while self.server.waitForDownload:
862 self.server.handle_request()
863
864 # Second chunk of data:
865 self.wfile.write("*" * size_chunk2)
866 return True
867
868 def DownloadFinishHandler(self):
869 """This handler just tells the server to finish the current download."""
870
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000871 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000872 return False
873
874 self.server.waitForDownload = False
875 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000876 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000877 self.send_header('Cache-Control', 'max-age=0')
878 self.end_headers()
879 return True
880
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000881 def _ReplaceFileData(self, data, query_parameters):
882 """Replaces matching substrings in a file.
883
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000884 If the 'replace_text' URL query parameter is present, it is expected to be
885 of the form old_text:new_text, which indicates that any old_text strings in
886 the file are replaced with new_text. Multiple 'replace_text' parameters may
887 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000888
889 If the parameters are not present, |data| is returned.
890 """
891 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000892 replace_text_values = query_dict.get('replace_text', [])
893 for replace_text_value in replace_text_values:
894 replace_text_args = replace_text_value.split(':')
895 if len(replace_text_args) != 2:
896 raise ValueError(
897 'replace_text must be of form old_text:new_text. Actual value: %s' %
898 replace_text_value)
899 old_text_b64, new_text_b64 = replace_text_args
900 old_text = base64.urlsafe_b64decode(old_text_b64)
901 new_text = base64.urlsafe_b64decode(new_text_b64)
902 data = data.replace(old_text, new_text)
903 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000904
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000905 def ZipFileHandler(self):
906 """This handler sends the contents of the requested file in compressed form.
907 Can pass in a parameter that specifies that the content length be
908 C - the compressed size (OK),
909 U - the uncompressed size (Non-standard, but handled),
910 S - less than compressed (OK because we keep going),
911 M - larger than compressed but less than uncompressed (an error),
912 L - larger than uncompressed (an error)
913 Example: compressedfiles/Picture_1.doc?C
914 """
915
916 prefix = "/compressedfiles/"
917 if not self.path.startswith(prefix):
918 return False
919
920 # Consume a request body if present.
921 if self.command == 'POST' or self.command == 'PUT' :
922 self.ReadRequestBody()
923
924 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
925
926 if not query in ('C', 'U', 'S', 'M', 'L'):
927 return False
928
929 sub_path = url_path[len(prefix):]
930 entries = sub_path.split('/')
931 file_path = os.path.join(self.server.data_dir, *entries)
932 if os.path.isdir(file_path):
933 file_path = os.path.join(file_path, 'index.html')
934
935 if not os.path.isfile(file_path):
936 print "File not found " + sub_path + " full path:" + file_path
937 self.send_error(404)
938 return True
939
940 f = open(file_path, "rb")
941 data = f.read()
942 uncompressed_len = len(data)
943 f.close()
944
945 # Compress the data.
946 data = zlib.compress(data)
947 compressed_len = len(data)
948
949 content_length = compressed_len
950 if query == 'U':
951 content_length = uncompressed_len
952 elif query == 'S':
953 content_length = compressed_len / 2
954 elif query == 'M':
955 content_length = (compressed_len + uncompressed_len) / 2
956 elif query == 'L':
957 content_length = compressed_len + uncompressed_len
958
959 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000960 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000961 self.send_header('Content-encoding', 'deflate')
962 self.send_header('Connection', 'close')
963 self.send_header('Content-Length', content_length)
964 self.send_header('ETag', '\'' + file_path + '\'')
965 self.end_headers()
966
967 self.wfile.write(data)
968
969 return True
970
initial.commit94958cf2008-07-26 22:42:52 +0000971 def FileHandler(self):
972 """This handler sends the contents of the requested file. Wow, it's like
973 a real webserver!"""
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000974 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000975 if not self.path.startswith(prefix):
976 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000977 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000978
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000979 def PostOnlyFileHandler(self):
980 """This handler sends the contents of the requested file on a POST."""
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000981 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000982 if not self.path.startswith(prefix):
983 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000984 return self._FileHandlerHelper(prefix)
985
986 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000987 request_body = ''
988 if self.command == 'POST' or self.command == 'PUT':
989 # Consume a request body if present.
990 request_body = self.ReadRequestBody()
991
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000992 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000993 query_dict = cgi.parse_qs(query)
994
995 expected_body = query_dict.get('expected_body', [])
996 if expected_body and request_body not in expected_body:
997 self.send_response(404)
998 self.end_headers()
999 self.wfile.write('')
1000 return True
1001
1002 expected_headers = query_dict.get('expected_headers', [])
1003 for expected_header in expected_headers:
1004 header_name, expected_value = expected_header.split(':')
1005 if self.headers.getheader(header_name) != expected_value:
1006 self.send_response(404)
1007 self.end_headers()
1008 self.wfile.write('')
1009 return True
1010
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001011 sub_path = url_path[len(prefix):]
1012 entries = sub_path.split('/')
1013 file_path = os.path.join(self.server.data_dir, *entries)
1014 if os.path.isdir(file_path):
1015 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +00001016
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001017 if not os.path.isfile(file_path):
1018 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +00001019 self.send_error(404)
1020 return True
1021
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001022 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +00001023 data = f.read()
1024 f.close()
1025
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001026 data = self._ReplaceFileData(data, query)
1027
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +00001028 old_protocol_version = self.protocol_version
1029
initial.commit94958cf2008-07-26 22:42:52 +00001030 # If file.mock-http-headers exists, it contains the headers we
1031 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +00001032 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +00001033 if os.path.isfile(headers_path):
1034 f = open(headers_path, "r")
1035
1036 # "HTTP/1.1 200 OK"
1037 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001038 http_major, http_minor, status_code = re.findall(
1039 'HTTP/(\d+).(\d+) (\d+)', response)[0]
1040 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +00001041 self.send_response(int(status_code))
1042
1043 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001044 header_values = re.findall('(\S+):\s*(.*)', line)
1045 if len(header_values) > 0:
1046 # "name: value"
1047 name, value = header_values[0]
1048 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001049 f.close()
1050 else:
1051 # Could be more generic once we support mime-type sniffing, but for
1052 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001053
1054 range = self.headers.get('Range')
1055 if range and range.startswith('bytes='):
shishir@chromium.orge6444822011-12-09 02:45:44 +00001056 # Note this doesn't handle all valid byte range values (i.e. left
1057 # open ended ones), just enough for what we needed so far.
jam@chromium.org41550782010-11-17 23:47:50 +00001058 range = range[6:].split('-')
1059 start = int(range[0])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001060 if range[1]:
1061 end = int(range[1])
1062 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001063 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001064
1065 self.send_response(206)
1066 content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \
1067 str(len(data))
1068 self.send_header('Content-Range', content_range)
1069 data = data[start: end + 1]
1070 else:
1071 self.send_response(200)
1072
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001073 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001074 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001075 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001076 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001077 self.end_headers()
1078
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001079 if (self.command != 'HEAD'):
1080 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001081
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001082 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001083 return True
1084
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001085 def SetCookieHandler(self):
1086 """This handler just sets a cookie, for testing cookie handling."""
1087
1088 if not self._ShouldHandleRequest("/set-cookie"):
1089 return False
1090
1091 query_char = self.path.find('?')
1092 if query_char != -1:
1093 cookie_values = self.path[query_char + 1:].split('&')
1094 else:
1095 cookie_values = ("",)
1096 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001097 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001098 for cookie_value in cookie_values:
1099 self.send_header('Set-Cookie', '%s' % cookie_value)
1100 self.end_headers()
1101 for cookie_value in cookie_values:
1102 self.wfile.write('%s' % cookie_value)
1103 return True
1104
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001105 def SetManyCookiesHandler(self):
1106 """This handler just sets a given number of cookies, for testing handling
1107 of large numbers of cookies."""
1108
1109 if not self._ShouldHandleRequest("/set-many-cookies"):
1110 return False
1111
1112 query_char = self.path.find('?')
1113 if query_char != -1:
1114 num_cookies = int(self.path[query_char + 1:])
1115 else:
1116 num_cookies = 0
1117 self.send_response(200)
1118 self.send_header('', 'text/html')
1119 for i in range(0, num_cookies):
1120 self.send_header('Set-Cookie', 'a=')
1121 self.end_headers()
1122 self.wfile.write('%d cookies were sent' % num_cookies)
1123 return True
1124
mattm@chromium.org983fc462012-06-30 00:52:08 +00001125 def ExpectAndSetCookieHandler(self):
1126 """Expects some cookies to be sent, and if they are, sets more cookies.
1127
1128 The expect parameter specifies a required cookie. May be specified multiple
1129 times.
1130 The set parameter specifies a cookie to set if all required cookies are
1131 preset. May be specified multiple times.
1132 The data parameter specifies the response body data to be returned."""
1133
1134 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1135 return False
1136
1137 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1138 query_dict = cgi.parse_qs(query)
1139 cookies = set()
1140 if 'Cookie' in self.headers:
1141 cookie_header = self.headers.getheader('Cookie')
1142 cookies.update([s.strip() for s in cookie_header.split(';')])
1143 got_all_expected_cookies = True
1144 for expected_cookie in query_dict.get('expect', []):
1145 if expected_cookie not in cookies:
1146 got_all_expected_cookies = False
1147 self.send_response(200)
1148 self.send_header('Content-Type', 'text/html')
1149 if got_all_expected_cookies:
1150 for cookie_value in query_dict.get('set', []):
1151 self.send_header('Set-Cookie', '%s' % cookie_value)
1152 self.end_headers()
1153 for data_value in query_dict.get('data', []):
1154 self.wfile.write(data_value)
1155 return True
1156
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001157 def SetHeaderHandler(self):
1158 """This handler sets a response header. Parameters are in the
1159 key%3A%20value&key2%3A%20value2 format."""
1160
1161 if not self._ShouldHandleRequest("/set-header"):
1162 return False
1163
1164 query_char = self.path.find('?')
1165 if query_char != -1:
1166 headers_values = self.path[query_char + 1:].split('&')
1167 else:
1168 headers_values = ("",)
1169 self.send_response(200)
1170 self.send_header('Content-Type', 'text/html')
1171 for header_value in headers_values:
1172 header_value = urllib.unquote(header_value)
1173 (key, value) = header_value.split(': ', 1)
1174 self.send_header(key, value)
1175 self.end_headers()
1176 for header_value in headers_values:
1177 self.wfile.write('%s' % header_value)
1178 return True
1179
initial.commit94958cf2008-07-26 22:42:52 +00001180 def AuthBasicHandler(self):
1181 """This handler tests 'Basic' authentication. It just sends a page with
1182 title 'user/pass' if you succeed."""
1183
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001184 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001185 return False
1186
1187 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001188 expected_password = 'secret'
1189 realm = 'testrealm'
1190 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001191
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001192 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1193 query_params = cgi.parse_qs(query, True)
1194 if 'set-cookie-if-challenged' in query_params:
1195 set_cookie_if_challenged = True
1196 if 'password' in query_params:
1197 expected_password = query_params['password'][0]
1198 if 'realm' in query_params:
1199 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001200
initial.commit94958cf2008-07-26 22:42:52 +00001201 auth = self.headers.getheader('authorization')
1202 try:
1203 if not auth:
1204 raise Exception('no auth')
1205 b64str = re.findall(r'Basic (\S+)', auth)[0]
1206 userpass = base64.b64decode(b64str)
1207 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001208 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001209 raise Exception('wrong password')
1210 except Exception, e:
1211 # Authentication failed.
1212 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001213 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001214 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001215 if set_cookie_if_challenged:
1216 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001217 self.end_headers()
1218 self.wfile.write('<html><head>')
1219 self.wfile.write('<title>Denied: %s</title>' % e)
1220 self.wfile.write('</head><body>')
1221 self.wfile.write('auth=%s<p>' % auth)
1222 self.wfile.write('b64str=%s<p>' % b64str)
1223 self.wfile.write('username: %s<p>' % username)
1224 self.wfile.write('userpass: %s<p>' % userpass)
1225 self.wfile.write('password: %s<p>' % password)
1226 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1227 self.wfile.write('</body></html>')
1228 return True
1229
1230 # Authentication successful. (Return a cachable response to allow for
1231 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001232 old_protocol_version = self.protocol_version
1233 self.protocol_version = "HTTP/1.1"
1234
initial.commit94958cf2008-07-26 22:42:52 +00001235 if_none_match = self.headers.getheader('if-none-match')
1236 if if_none_match == "abc":
1237 self.send_response(304)
1238 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001239 elif url_path.endswith(".gif"):
1240 # Using chrome/test/data/google/logo.gif as the test image
1241 test_image_path = ['google', 'logo.gif']
1242 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1243 if not os.path.isfile(gif_path):
1244 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001245 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001246 return True
1247
1248 f = open(gif_path, "rb")
1249 data = f.read()
1250 f.close()
1251
1252 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001253 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001254 self.send_header('Cache-control', 'max-age=60000')
1255 self.send_header('Etag', 'abc')
1256 self.end_headers()
1257 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001258 else:
1259 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001260 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001261 self.send_header('Cache-control', 'max-age=60000')
1262 self.send_header('Etag', 'abc')
1263 self.end_headers()
1264 self.wfile.write('<html><head>')
1265 self.wfile.write('<title>%s/%s</title>' % (username, password))
1266 self.wfile.write('</head><body>')
1267 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001268 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001269 self.wfile.write('</body></html>')
1270
rvargas@google.com54453b72011-05-19 01:11:11 +00001271 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001272 return True
1273
satorux@chromium.orgfdc70122012-03-07 18:08:41 +00001274 def GDataAuthHandler(self):
1275 """This handler verifies the Authentication header for GData requests."""
1276 if not self.server.gdata_auth_token:
1277 # --auth-token is not specified, not the test case for GData.
1278 return False
1279
1280 if not self._ShouldHandleRequest('/files/chromeos/gdata'):
1281 return False
1282
1283 if 'GData-Version' not in self.headers:
1284 self.send_error(httplib.BAD_REQUEST, 'GData-Version header is missing.')
1285 return True
1286
1287 if 'Authorization' not in self.headers:
1288 self.send_error(httplib.UNAUTHORIZED)
1289 return True
1290
1291 field_prefix = 'Bearer '
1292 authorization = self.headers['Authorization']
1293 if not authorization.startswith(field_prefix):
1294 self.send_error(httplib.UNAUTHORIZED)
1295 return True
1296
1297 code = authorization[len(field_prefix):]
1298 if code != self.server.gdata_auth_token:
1299 self.send_error(httplib.UNAUTHORIZED)
1300 return True
1301
1302 return False
1303
1304 def GDataDocumentsFeedQueryHandler(self):
1305 """This handler verifies if required parameters are properly
1306 specified for the GData DocumentsFeed request."""
1307 if not self.server.gdata_auth_token:
1308 # --auth-token is not specified, not the test case for GData.
1309 return False
1310
1311 if not self._ShouldHandleRequest('/files/chromeos/gdata/root_feed.json'):
1312 return False
1313
1314 (path, question, query_params) = self.path.partition('?')
1315 self.query_params = urlparse.parse_qs(query_params)
1316
1317 if 'v' not in self.query_params:
1318 self.send_error(httplib.BAD_REQUEST, 'v is not specified.')
1319 return True
1320 elif 'alt' not in self.query_params or self.query_params['alt'] != ['json']:
1321 # currently our GData client only uses JSON format.
1322 self.send_error(httplib.BAD_REQUEST, 'alt parameter is wrong.')
1323 return True
1324
1325 return False
1326
tonyg@chromium.org75054202010-03-31 22:06:10 +00001327 def GetNonce(self, force_reset=False):
1328 """Returns a nonce that's stable per request path for the server's lifetime.
initial.commit94958cf2008-07-26 22:42:52 +00001329
tonyg@chromium.org75054202010-03-31 22:06:10 +00001330 This is a fake implementation. A real implementation would only use a given
1331 nonce a single time (hence the name n-once). However, for the purposes of
1332 unittesting, we don't care about the security of the nonce.
1333
1334 Args:
1335 force_reset: Iff set, the nonce will be changed. Useful for testing the
1336 "stale" response.
1337 """
1338 if force_reset or not self.server.nonce_time:
1339 self.server.nonce_time = time.time()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001340 return hashlib.md5('privatekey%s%d' %
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001341 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001342
1343 def AuthDigestHandler(self):
1344 """This handler tests 'Digest' authentication.
1345
1346 It just sends a page with title 'user/pass' if you succeed.
1347
1348 A stale response is sent iff "stale" is present in the request path.
1349 """
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001350 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001351 return False
1352
tonyg@chromium.org75054202010-03-31 22:06:10 +00001353 stale = 'stale' in self.path
1354 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001355 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001356 password = 'secret'
1357 realm = 'testrealm'
1358
1359 auth = self.headers.getheader('authorization')
1360 pairs = {}
1361 try:
1362 if not auth:
1363 raise Exception('no auth')
1364 if not auth.startswith('Digest'):
1365 raise Exception('not digest')
1366 # Pull out all the name="value" pairs as a dictionary.
1367 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1368
1369 # Make sure it's all valid.
1370 if pairs['nonce'] != nonce:
1371 raise Exception('wrong nonce')
1372 if pairs['opaque'] != opaque:
1373 raise Exception('wrong opaque')
1374
1375 # Check the 'response' value and make sure it matches our magic hash.
1376 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001377 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001378 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001379 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001380 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001381 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001382 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1383 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001384 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001385
1386 if pairs['response'] != response:
1387 raise Exception('wrong password')
1388 except Exception, e:
1389 # Authentication failed.
1390 self.send_response(401)
1391 hdr = ('Digest '
1392 'realm="%s", '
1393 'domain="/", '
1394 'qop="auth", '
1395 'algorithm=MD5, '
1396 'nonce="%s", '
1397 'opaque="%s"') % (realm, nonce, opaque)
1398 if stale:
1399 hdr += ', stale="TRUE"'
1400 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001401 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001402 self.end_headers()
1403 self.wfile.write('<html><head>')
1404 self.wfile.write('<title>Denied: %s</title>' % e)
1405 self.wfile.write('</head><body>')
1406 self.wfile.write('auth=%s<p>' % auth)
1407 self.wfile.write('pairs=%s<p>' % pairs)
1408 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1409 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1410 self.wfile.write('</body></html>')
1411 return True
1412
1413 # Authentication successful.
1414 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001416 self.end_headers()
1417 self.wfile.write('<html><head>')
1418 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1419 self.wfile.write('</head><body>')
1420 self.wfile.write('auth=%s<p>' % auth)
1421 self.wfile.write('pairs=%s<p>' % pairs)
1422 self.wfile.write('</body></html>')
1423
1424 return True
1425
1426 def SlowServerHandler(self):
1427 """Wait for the user suggested time before responding. The syntax is
1428 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001429 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001430 return False
1431 query_char = self.path.find('?')
1432 wait_sec = 1.0
1433 if query_char >= 0:
1434 try:
1435 wait_sec = int(self.path[query_char + 1:])
1436 except ValueError:
1437 pass
1438 time.sleep(wait_sec)
1439 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001440 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001441 self.end_headers()
1442 self.wfile.write("waited %d seconds" % wait_sec)
1443 return True
1444
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001445 def ChunkedServerHandler(self):
1446 """Send chunked response. Allows to specify chunks parameters:
1447 - waitBeforeHeaders - ms to wait before sending headers
1448 - waitBetweenChunks - ms to wait between chunks
1449 - chunkSize - size of each chunk in bytes
1450 - chunksNumber - number of chunks
1451 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1452 waits one second, then sends headers and five chunks five bytes each."""
1453 if not self._ShouldHandleRequest("/chunked"):
1454 return False
1455 query_char = self.path.find('?')
1456 chunkedSettings = {'waitBeforeHeaders' : 0,
1457 'waitBetweenChunks' : 0,
1458 'chunkSize' : 5,
1459 'chunksNumber' : 5}
1460 if query_char >= 0:
1461 params = self.path[query_char + 1:].split('&')
1462 for param in params:
1463 keyValue = param.split('=')
1464 if len(keyValue) == 2:
1465 try:
1466 chunkedSettings[keyValue[0]] = int(keyValue[1])
1467 except ValueError:
1468 pass
1469 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']);
1470 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1471 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001472 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001473 self.send_header('Connection', 'close')
1474 self.send_header('Transfer-Encoding', 'chunked')
1475 self.end_headers()
1476 # Chunked encoding: sending all chunks, then final zero-length chunk and
1477 # then final CRLF.
1478 for i in range(0, chunkedSettings['chunksNumber']):
1479 if i > 0:
1480 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1481 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1482 self.wfile.flush(); # Keep in mind that we start flushing only after 1kb.
1483 self.sendChunkHelp('')
1484 return True
1485
initial.commit94958cf2008-07-26 22:42:52 +00001486 def ContentTypeHandler(self):
1487 """Returns a string of html with the given content type. E.g.,
1488 /contenttype?text/css returns an html file with the Content-Type
1489 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001490 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001491 return False
1492 query_char = self.path.find('?')
1493 content_type = self.path[query_char + 1:].strip()
1494 if not content_type:
1495 content_type = 'text/html'
1496 self.send_response(200)
1497 self.send_header('Content-Type', content_type)
1498 self.end_headers()
1499 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
1500 return True
1501
creis@google.com2f4f6a42011-03-25 19:44:19 +00001502 def NoContentHandler(self):
1503 """Returns a 204 No Content response."""
1504 if not self._ShouldHandleRequest("/nocontent"):
1505 return False
1506 self.send_response(204)
1507 self.end_headers()
1508 return True
1509
initial.commit94958cf2008-07-26 22:42:52 +00001510 def ServerRedirectHandler(self):
1511 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001512 '/server-redirect?http://foo.bar/asdf' to redirect to
1513 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001514
1515 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001516 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001517 return False
1518
1519 query_char = self.path.find('?')
1520 if query_char < 0 or len(self.path) <= query_char + 1:
1521 self.sendRedirectHelp(test_name)
1522 return True
1523 dest = self.path[query_char + 1:]
1524
1525 self.send_response(301) # moved permanently
1526 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001527 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001528 self.end_headers()
1529 self.wfile.write('<html><head>')
1530 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1531
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001532 return True
initial.commit94958cf2008-07-26 22:42:52 +00001533
1534 def ClientRedirectHandler(self):
1535 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001536 '/client-redirect?http://foo.bar/asdf' to redirect to
1537 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001538
1539 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001540 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001541 return False
1542
1543 query_char = self.path.find('?');
1544 if query_char < 0 or len(self.path) <= query_char + 1:
1545 self.sendRedirectHelp(test_name)
1546 return True
1547 dest = self.path[query_char + 1:]
1548
1549 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001550 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001551 self.end_headers()
1552 self.wfile.write('<html><head>')
1553 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1554 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1555
1556 return True
1557
tony@chromium.org03266982010-03-05 03:18:42 +00001558 def MultipartHandler(self):
1559 """Send a multipart response (10 text/html pages)."""
tony@chromium.org4cb88302011-09-27 22:13:49 +00001560 test_name = '/multipart'
tony@chromium.org03266982010-03-05 03:18:42 +00001561 if not self._ShouldHandleRequest(test_name):
1562 return False
1563
1564 num_frames = 10
1565 bound = '12345'
1566 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001567 self.send_header('Content-Type',
tony@chromium.org03266982010-03-05 03:18:42 +00001568 'multipart/x-mixed-replace;boundary=' + bound)
1569 self.end_headers()
1570
1571 for i in xrange(num_frames):
1572 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001573 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org03266982010-03-05 03:18:42 +00001574 self.wfile.write('<title>page ' + str(i) + '</title>')
1575 self.wfile.write('page ' + str(i))
1576
1577 self.wfile.write('--' + bound + '--')
1578 return True
1579
tony@chromium.org4cb88302011-09-27 22:13:49 +00001580 def MultipartSlowHandler(self):
1581 """Send a multipart response (3 text/html pages) with a slight delay
1582 between each page. This is similar to how some pages show status using
1583 multipart."""
1584 test_name = '/multipart-slow'
1585 if not self._ShouldHandleRequest(test_name):
1586 return False
1587
1588 num_frames = 3
1589 bound = '12345'
1590 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001591 self.send_header('Content-Type',
tony@chromium.org4cb88302011-09-27 22:13:49 +00001592 'multipart/x-mixed-replace;boundary=' + bound)
1593 self.end_headers()
1594
1595 for i in xrange(num_frames):
1596 self.wfile.write('--' + bound + '\r\n')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001597 self.wfile.write('Content-Type: text/html\r\n\r\n')
tony@chromium.org4cb88302011-09-27 22:13:49 +00001598 time.sleep(0.25)
1599 if i == 2:
1600 self.wfile.write('<title>PASS</title>')
1601 else:
1602 self.wfile.write('<title>page ' + str(i) + '</title>')
1603 self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1604
1605 self.wfile.write('--' + bound + '--')
1606 return True
1607
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001608 def GetSSLSessionCacheHandler(self):
1609 """Send a reply containing a log of the session cache operations."""
1610
1611 if not self._ShouldHandleRequest('/ssl-session-cache'):
1612 return False
1613
1614 self.send_response(200)
1615 self.send_header('Content-Type', 'text/plain')
1616 self.end_headers()
1617 try:
1618 for (action, sessionID) in self.server.session_cache.log:
1619 self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1620 except AttributeError, e:
1621 self.wfile.write('Pass --https-record-resume in order to use' +
1622 ' this request')
1623 return True
1624
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001625 def CloseSocketHandler(self):
1626 """Closes the socket without sending anything."""
1627
1628 if not self._ShouldHandleRequest('/close-socket'):
1629 return False
1630
1631 self.wfile.close()
1632 return True
1633
initial.commit94958cf2008-07-26 22:42:52 +00001634 def DefaultResponseHandler(self):
1635 """This is the catch-all response handler for requests that aren't handled
1636 by one of the special handlers above.
1637 Note that we specify the content-length as without it the https connection
1638 is not closed properly (and the browser keeps expecting data)."""
1639
1640 contents = "Default response given for path: " + self.path
1641 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001642 self.send_header('Content-Type', 'text/html')
1643 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001644 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001645 if (self.command != 'HEAD'):
1646 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001647 return True
1648
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001649 def RedirectConnectHandler(self):
1650 """Sends a redirect to the CONNECT request for www.redirect.com. This
1651 response is not specified by the RFC, so the browser should not follow
1652 the redirect."""
1653
1654 if (self.path.find("www.redirect.com") < 0):
1655 return False
1656
1657 dest = "http://www.destination.com/foo.js"
1658
1659 self.send_response(302) # moved temporarily
1660 self.send_header('Location', dest)
1661 self.send_header('Connection', 'close')
1662 self.end_headers()
1663 return True
1664
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001665 def ServerAuthConnectHandler(self):
1666 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1667 response doesn't make sense because the proxy server cannot request
1668 server authentication."""
1669
1670 if (self.path.find("www.server-auth.com") < 0):
1671 return False
1672
1673 challenge = 'Basic realm="WallyWorld"'
1674
1675 self.send_response(401) # unauthorized
1676 self.send_header('WWW-Authenticate', challenge)
1677 self.send_header('Connection', 'close')
1678 self.end_headers()
1679 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001680
1681 def DefaultConnectResponseHandler(self):
1682 """This is the catch-all response handler for CONNECT requests that aren't
1683 handled by one of the special handlers above. Real Web servers respond
1684 with 400 to CONNECT requests."""
1685
1686 contents = "Your client has issued a malformed or illegal request."
1687 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001688 self.send_header('Content-Type', 'text/html')
1689 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001690 self.end_headers()
1691 self.wfile.write(contents)
1692 return True
1693
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001694 def DeviceManagementHandler(self):
1695 """Delegates to the device management service used for cloud policy."""
1696 if not self._ShouldHandleRequest("/device_management"):
1697 return False
1698
satish@chromium.orgce0b1d02011-01-25 07:17:11 +00001699 raw_request = self.ReadRequestBody()
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001700
1701 if not self.server._device_management_handler:
1702 import device_management
1703 policy_path = os.path.join(self.server.data_dir, 'device_management')
1704 self.server._device_management_handler = (
gfeher@chromium.orge0bddc12011-01-28 18:15:24 +00001705 device_management.TestServer(policy_path,
mnissler@chromium.orgcfe39282011-04-11 12:13:05 +00001706 self.server.policy_keys,
1707 self.server.policy_user))
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001708
1709 http_response, raw_reply = (
1710 self.server._device_management_handler.HandleRequest(self.path,
1711 self.headers,
1712 raw_request))
1713 self.send_response(http_response)
joaodasilva@chromium.orgd3a1ca52011-09-28 20:46:20 +00001714 if (http_response == 200):
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001715 self.send_header('Content-Type', 'application/x-protobuffer')
mnissler@chromium.org7c939802010-11-11 08:47:14 +00001716 self.end_headers()
1717 self.wfile.write(raw_reply)
1718 return True
1719
initial.commit94958cf2008-07-26 22:42:52 +00001720 # called by the redirect handling function when there is no parameter
1721 def sendRedirectHelp(self, redirect_name):
1722 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001723 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001724 self.end_headers()
1725 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1726 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1727 self.wfile.write('</body></html>')
1728
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001729 # called by chunked handling function
1730 def sendChunkHelp(self, chunk):
1731 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1732 self.wfile.write('%X\r\n' % len(chunk))
1733 self.wfile.write(chunk)
1734 self.wfile.write('\r\n')
1735
akalin@chromium.org154bb132010-11-12 02:20:27 +00001736
1737class SyncPageHandler(BasePageHandler):
1738 """Handler for the main HTTP sync server."""
1739
1740 def __init__(self, request, client_address, sync_http_server):
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001741 get_handlers = [self.ChromiumSyncTimeHandler,
1742 self.ChromiumSyncMigrationOpHandler,
1743 self.ChromiumSyncCredHandler,
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001744 self.ChromiumSyncDisableNotificationsOpHandler,
1745 self.ChromiumSyncEnableNotificationsOpHandler,
1746 self.ChromiumSyncSendNotificationOpHandler,
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001747 self.ChromiumSyncBirthdayErrorOpHandler,
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001748 self.ChromiumSyncTransientErrorOpHandler,
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001749 self.ChromiumSyncErrorOpHandler,
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001750 self.ChromiumSyncSyncTabFaviconsOpHandler,
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001751 self.ChromiumSyncCreateSyncedBookmarksOpHandler]
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001752
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001753 post_handlers = [self.ChromiumSyncCommandHandler,
1754 self.ChromiumSyncTimeHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +00001755 BasePageHandler.__init__(self, request, client_address,
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001756 sync_http_server, [], get_handlers, [],
akalin@chromium.org154bb132010-11-12 02:20:27 +00001757 post_handlers, [])
1758
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001759
akalin@chromium.org154bb132010-11-12 02:20:27 +00001760 def ChromiumSyncTimeHandler(self):
1761 """Handle Chromium sync .../time requests.
1762
1763 The syncer sometimes checks server reachability by examining /time.
1764 """
1765 test_name = "/chromiumsync/time"
1766 if not self._ShouldHandleRequest(test_name):
1767 return False
1768
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001769 # Chrome hates it if we send a response before reading the request.
1770 if self.headers.getheader('content-length'):
1771 length = int(self.headers.getheader('content-length'))
1772 raw_request = self.rfile.read(length)
1773
akalin@chromium.org154bb132010-11-12 02:20:27 +00001774 self.send_response(200)
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001775 self.send_header('Content-Type', 'text/plain')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001776 self.end_headers()
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001777 self.wfile.write('0123456789')
akalin@chromium.org154bb132010-11-12 02:20:27 +00001778 return True
1779
1780 def ChromiumSyncCommandHandler(self):
1781 """Handle a chromiumsync command arriving via http.
1782
1783 This covers all sync protocol commands: authentication, getupdates, and
1784 commit.
1785 """
1786 test_name = "/chromiumsync/command"
1787 if not self._ShouldHandleRequest(test_name):
1788 return False
1789
satish@chromium.orgc5228b12011-01-25 12:13:19 +00001790 length = int(self.headers.getheader('content-length'))
1791 raw_request = self.rfile.read(length)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001792 http_response = 200
1793 raw_reply = None
1794 if not self.server.GetAuthenticated():
1795 http_response = 401
erikwright@chromium.org847ef282012-02-22 16:41:10 +00001796 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
1797 self.server.server_address[0])
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001798 else:
1799 http_response, raw_reply = self.server.HandleCommand(
1800 self.path, raw_request)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001801
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001802 ### Now send the response to the client. ###
akalin@chromium.org154bb132010-11-12 02:20:27 +00001803 self.send_response(http_response)
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001804 if http_response == 401:
1805 self.send_header('www-Authenticate', challenge)
akalin@chromium.org154bb132010-11-12 02:20:27 +00001806 self.end_headers()
1807 self.wfile.write(raw_reply)
1808 return True
1809
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001810 def ChromiumSyncMigrationOpHandler(self):
nick@chromium.org45dcfa82011-04-23 01:53:16 +00001811 test_name = "/chromiumsync/migrate"
1812 if not self._ShouldHandleRequest(test_name):
1813 return False
1814
1815 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
1816 self.path)
1817 self.send_response(http_response)
1818 self.send_header('Content-Type', 'text/html')
1819 self.send_header('Content-Length', len(raw_reply))
1820 self.end_headers()
1821 self.wfile.write(raw_reply)
1822 return True
1823
lipalani@chromium.org0f7e6092011-09-23 01:26:10 +00001824 def ChromiumSyncCredHandler(self):
1825 test_name = "/chromiumsync/cred"
1826 if not self._ShouldHandleRequest(test_name):
1827 return False
1828 try:
1829 query = urlparse.urlparse(self.path)[4]
1830 cred_valid = urlparse.parse_qs(query)['valid']
1831 if cred_valid[0] == 'True':
1832 self.server.SetAuthenticated(True)
1833 else:
1834 self.server.SetAuthenticated(False)
1835 except:
1836 self.server.SetAuthenticated(False)
1837
1838 http_response = 200
1839 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
1840 self.send_response(http_response)
1841 self.send_header('Content-Type', 'text/html')
1842 self.send_header('Content-Length', len(raw_reply))
1843 self.end_headers()
1844 self.wfile.write(raw_reply)
1845 return True
1846
akalin@chromium.org0332ec72011-08-27 06:53:21 +00001847 def ChromiumSyncDisableNotificationsOpHandler(self):
1848 test_name = "/chromiumsync/disablenotifications"
1849 if not self._ShouldHandleRequest(test_name):
1850 return False
1851 self.server.GetXmppServer().DisableNotifications()
1852 result = 200
1853 raw_reply = ('<html><title>Notifications disabled</title>'
1854 '<H1>Notifications disabled</H1></html>')
1855 self.send_response(result)
1856 self.send_header('Content-Type', 'text/html')
1857 self.send_header('Content-Length', len(raw_reply))
1858 self.end_headers()
1859 self.wfile.write(raw_reply)
1860 return True;
1861
1862 def ChromiumSyncEnableNotificationsOpHandler(self):
1863 test_name = "/chromiumsync/enablenotifications"
1864 if not self._ShouldHandleRequest(test_name):
1865 return False
1866 self.server.GetXmppServer().EnableNotifications()
1867 result = 200
1868 raw_reply = ('<html><title>Notifications enabled</title>'
1869 '<H1>Notifications enabled</H1></html>')
1870 self.send_response(result)
1871 self.send_header('Content-Type', 'text/html')
1872 self.send_header('Content-Length', len(raw_reply))
1873 self.end_headers()
1874 self.wfile.write(raw_reply)
1875 return True;
1876
1877 def ChromiumSyncSendNotificationOpHandler(self):
1878 test_name = "/chromiumsync/sendnotification"
1879 if not self._ShouldHandleRequest(test_name):
1880 return False
1881 query = urlparse.urlparse(self.path)[4]
1882 query_params = urlparse.parse_qs(query)
1883 channel = ''
1884 data = ''
1885 if 'channel' in query_params:
1886 channel = query_params['channel'][0]
1887 if 'data' in query_params:
1888 data = query_params['data'][0]
1889 self.server.GetXmppServer().SendNotification(channel, data)
1890 result = 200
1891 raw_reply = ('<html><title>Notification sent</title>'
1892 '<H1>Notification sent with channel "%s" '
1893 'and data "%s"</H1></html>'
1894 % (channel, data))
1895 self.send_response(result)
1896 self.send_header('Content-Type', 'text/html')
1897 self.send_header('Content-Length', len(raw_reply))
1898 self.end_headers()
1899 self.wfile.write(raw_reply)
1900 return True;
1901
lipalani@chromium.orgb56c12b2011-07-30 02:17:37 +00001902 def ChromiumSyncBirthdayErrorOpHandler(self):
1903 test_name = "/chromiumsync/birthdayerror"
1904 if not self._ShouldHandleRequest(test_name):
1905 return False
1906 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
1907 self.send_response(result)
1908 self.send_header('Content-Type', 'text/html')
1909 self.send_header('Content-Length', len(raw_reply))
1910 self.end_headers()
1911 self.wfile.write(raw_reply)
1912 return True;
1913
lipalani@chromium.orgf9737fc2011-08-12 05:37:47 +00001914 def ChromiumSyncTransientErrorOpHandler(self):
1915 test_name = "/chromiumsync/transienterror"
1916 if not self._ShouldHandleRequest(test_name):
1917 return False
1918 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
1919 self.send_response(result)
1920 self.send_header('Content-Type', 'text/html')
1921 self.send_header('Content-Length', len(raw_reply))
1922 self.end_headers()
1923 self.wfile.write(raw_reply)
1924 return True;
1925
lipalani@chromium.orgbabf5892011-09-22 23:14:08 +00001926 def ChromiumSyncErrorOpHandler(self):
1927 test_name = "/chromiumsync/error"
1928 if not self._ShouldHandleRequest(test_name):
1929 return False
1930 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
1931 self.path)
1932 self.send_response(result)
1933 self.send_header('Content-Type', 'text/html')
1934 self.send_header('Content-Length', len(raw_reply))
1935 self.end_headers()
1936 self.wfile.write(raw_reply)
1937 return True;
1938
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001939 def ChromiumSyncSyncTabFaviconsOpHandler(self):
1940 test_name = "/chromiumsync/synctabfavicons"
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001941 if not self._ShouldHandleRequest(test_name):
1942 return False
nyquist@chromium.org154389d2012-09-07 20:33:58 +00001943 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
zea@chromium.org1aa5e632011-08-25 22:21:02 +00001944 self.send_response(result)
1945 self.send_header('Content-Type', 'text/html')
1946 self.send_header('Content-Length', len(raw_reply))
1947 self.end_headers()
1948 self.wfile.write(raw_reply)
1949 return True;
1950
zea@chromium.orga606cbb2012-03-16 04:24:18 +00001951 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
1952 test_name = "/chromiumsync/createsyncedbookmarks"
1953 if not self._ShouldHandleRequest(test_name):
1954 return False
1955 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
1956 self.send_response(result)
1957 self.send_header('Content-Type', 'text/html')
1958 self.send_header('Content-Length', len(raw_reply))
1959 self.end_headers()
1960 self.wfile.write(raw_reply)
1961 return True;
1962
akalin@chromium.org154bb132010-11-12 02:20:27 +00001963
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00001964def MakeDataDir():
1965 if options.data_dir:
1966 if not os.path.isdir(options.data_dir):
1967 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1968 return None
1969 my_data_dir = options.data_dir
1970 else:
1971 # Create the default path to our data dir, relative to the exe dir.
1972 my_data_dir = os.path.dirname(sys.argv[0])
1973 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1974 "test", "data")
1975
1976 #TODO(ibrar): Must use Find* funtion defined in google\tools
1977 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1978
1979 return my_data_dir
1980
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001981class OCSPHandler(BasePageHandler):
1982 def __init__(self, request, client_address, socket_server):
1983 handlers = [self.OCSPResponse]
1984 self.ocsp_response = socket_server.ocsp_response
1985 BasePageHandler.__init__(self, request, client_address, socket_server,
1986 [], handlers, [], handlers, [])
1987
1988 def OCSPResponse(self):
1989 self.send_response(200)
1990 self.send_header('Content-Type', 'application/ocsp-response')
1991 self.send_header('Content-Length', str(len(self.ocsp_response)))
1992 self.end_headers()
1993
1994 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001995
1996class TCPEchoHandler(SocketServer.BaseRequestHandler):
1997 """The RequestHandler class for TCP echo server.
1998
1999 It is instantiated once per connection to the server, and overrides the
2000 handle() method to implement communication to the client.
2001 """
2002
2003 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002004 """Handles the request from the client and constructs a response."""
2005
2006 data = self.request.recv(65536).strip()
2007 # Verify the "echo request" message received from the client. Send back
2008 # "echo response" message if "echo request" message is valid.
2009 try:
2010 return_data = echo_message.GetEchoResponseData(data)
2011 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002012 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002013 except ValueError:
2014 return
2015
2016 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002017
2018
2019class UDPEchoHandler(SocketServer.BaseRequestHandler):
2020 """The RequestHandler class for UDP echo server.
2021
2022 It is instantiated once per connection to the server, and overrides the
2023 handle() method to implement communication to the client.
2024 """
2025
2026 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002027 """Handles the request from the client and constructs a response."""
2028
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002029 data = self.request[0].strip()
2030 socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00002031 # Verify the "echo request" message received from the client. Send back
2032 # "echo response" message if "echo request" message is valid.
2033 try:
2034 return_data = echo_message.GetEchoResponseData(data)
2035 if not return_data:
2036 return
2037 except ValueError:
2038 return
2039 socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00002040
2041
bashi@chromium.org33233532012-09-08 17:37:24 +00002042class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
2043 """A request handler that behaves as a proxy server which requires
2044 basic authentication. Only CONNECT, GET and HEAD is supported for now.
2045 """
2046
2047 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
2048
2049 def parse_request(self):
2050 """Overrides parse_request to check credential."""
2051
2052 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
2053 return False
2054
2055 auth = self.headers.getheader('Proxy-Authorization')
2056 if auth != self._AUTH_CREDENTIAL:
2057 self.send_response(407)
2058 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
2059 self.end_headers()
2060 return False
2061
2062 return True
2063
2064 def _start_read_write(self, sock):
2065 sock.setblocking(0)
2066 self.request.setblocking(0)
2067 rlist = [self.request, sock]
2068 while True:
2069 ready_sockets, unused, errors = select.select(rlist, [], [])
2070 if errors:
2071 self.send_response(500)
2072 self.end_headers()
2073 return
2074 for s in ready_sockets:
2075 received = s.recv(1024)
2076 if len(received) == 0:
2077 return
2078 if s == self.request:
2079 other = sock
2080 else:
2081 other = self.request
2082 other.send(received)
2083
2084 def _do_common_method(self):
2085 url = urlparse.urlparse(self.path)
2086 port = url.port
2087 if not port:
2088 if url.scheme == 'http':
2089 port = 80
2090 elif url.scheme == 'https':
2091 port = 443
2092 if not url.hostname or not port:
2093 self.send_response(400)
2094 self.end_headers()
2095 return
2096
2097 if len(url.path) == 0:
2098 path = '/'
2099 else:
2100 path = url.path
2101 if len(url.query) > 0:
2102 path = '%s?%s' % (url.path, url.query)
2103
2104 sock = None
2105 try:
2106 sock = socket.create_connection((url.hostname, port))
2107 sock.send('%s %s %s\r\n' % (
2108 self.command, path, self.protocol_version))
2109 for header in self.headers.headers:
2110 header = header.strip()
2111 if (header.lower().startswith('connection') or
2112 header.lower().startswith('proxy')):
2113 continue
2114 sock.send('%s\r\n' % header)
2115 sock.send('\r\n')
2116 self._start_read_write(sock)
2117 except:
2118 self.send_response(500)
2119 self.end_headers()
2120 finally:
2121 if sock is not None:
2122 sock.close()
2123
2124 def do_CONNECT(self):
2125 try:
2126 pos = self.path.rfind(':')
2127 host = self.path[:pos]
2128 port = int(self.path[pos+1:])
2129 except:
2130 self.send_response(400)
2131 self.end_headers()
2132
2133 try:
2134 sock = socket.create_connection((host, port))
2135 self.send_response(200, 'Connection established')
2136 self.end_headers()
2137 self._start_read_write(sock)
2138 except:
2139 self.send_response(500)
2140 self.end_headers()
2141 finally:
2142 sock.close()
2143
2144 def do_GET(self):
2145 self._do_common_method()
2146
2147 def do_HEAD(self):
2148 self._do_common_method()
2149
2150
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002151class FileMultiplexer:
2152 def __init__(self, fd1, fd2) :
2153 self.__fd1 = fd1
2154 self.__fd2 = fd2
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002155
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002156 def __del__(self) :
2157 if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
2158 self.__fd1.close()
2159 if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
2160 self.__fd2.close()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002161
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002162 def write(self, text) :
2163 self.__fd1.write(text)
2164 self.__fd2.write(text)
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002165
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002166 def flush(self) :
2167 self.__fd1.flush()
2168 self.__fd2.flush()
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00002169
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002170def main(options, args):
2171 logfile = open('testserver.log', 'w')
2172 sys.stderr = FileMultiplexer(sys.stderr, logfile)
2173 if options.log_to_console:
2174 sys.stdout = FileMultiplexer(sys.stdout, logfile)
2175 else:
2176 sys.stdout = logfile
initial.commit94958cf2008-07-26 22:42:52 +00002177
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002178 port = options.port
2179 host = options.host
initial.commit94958cf2008-07-26 22:42:52 +00002180
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002181 server_data = {}
2182 server_data['host'] = host
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002183
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002184 ocsp_server = None
agl@chromium.org77a9ad92012-03-20 15:14:27 +00002185
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002186 if options.server_type == SERVER_HTTP:
2187 if options.https:
2188 pem_cert_and_key = None
2189 if options.cert_and_key_file:
2190 if not os.path.isfile(options.cert_and_key_file):
2191 print ('specified server cert file not found: ' +
2192 options.cert_and_key_file + ' exiting...')
2193 return
2194 pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
mattm@chromium.org07e28412012-09-05 00:19:41 +00002195 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002196 # generate a new certificate and run an OCSP server for it.
2197 ocsp_server = OCSPServer((host, 0), OCSPHandler)
2198 print ('OCSP server started on %s:%d...' %
2199 (host, ocsp_server.server_port))
mattm@chromium.org07e28412012-09-05 00:19:41 +00002200
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002201 ocsp_der = None
2202 ocsp_state = None
mattm@chromium.org07e28412012-09-05 00:19:41 +00002203
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002204 if options.ocsp == 'ok':
2205 ocsp_state = minica.OCSP_STATE_GOOD
2206 elif options.ocsp == 'revoked':
2207 ocsp_state = minica.OCSP_STATE_REVOKED
2208 elif options.ocsp == 'invalid':
2209 ocsp_state = minica.OCSP_STATE_INVALID
2210 elif options.ocsp == 'unauthorized':
2211 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2212 elif options.ocsp == 'unknown':
2213 ocsp_state = minica.OCSP_STATE_UNKNOWN
2214 else:
2215 print 'unknown OCSP status: ' + options.ocsp_status
2216 return
mattm@chromium.org07e28412012-09-05 00:19:41 +00002217
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002218 (pem_cert_and_key, ocsp_der) = \
2219 minica.GenerateCertKeyAndOCSP(
2220 subject = "127.0.0.1",
2221 ocsp_url = ("http://%s:%d/ocsp" %
2222 (host, ocsp_server.server_port)),
2223 ocsp_state = ocsp_state)
mattm@chromium.org07e28412012-09-05 00:19:41 +00002224
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002225 ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org07e28412012-09-05 00:19:41 +00002226
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002227 for ca_cert in options.ssl_client_ca:
2228 if not os.path.isfile(ca_cert):
2229 print 'specified trusted client CA file not found: ' + ca_cert + \
2230 ' exiting...'
2231 return
2232 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2233 options.ssl_client_auth, options.ssl_client_ca,
2234 options.ssl_bulk_cipher, options.record_resume,
2235 options.tls_intolerant)
2236 print 'HTTPS server started on %s:%d...' % (host, server.server_port)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002237 else:
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002238 server = HTTPServer((host, port), TestPageHandler)
2239 print 'HTTP server started on %s:%d...' % (host, server.server_port)
erikkay@google.com70397b62008-12-30 21:49:21 +00002240
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002241 server.data_dir = MakeDataDir()
2242 server.file_root_url = options.file_root_url
2243 server_data['port'] = server.server_port
2244 server._device_management_handler = None
2245 server.policy_keys = options.policy_keys
2246 server.policy_user = options.policy_user
2247 server.gdata_auth_token = options.auth_token
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002248 elif options.server_type == SERVER_WEBSOCKET:
2249 # Launch pywebsocket via WebSocketServer.
2250 logger = logging.getLogger()
2251 logger.addHandler(logging.StreamHandler())
2252 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2253 # is required to work correctly. It should be fixed from pywebsocket side.
2254 os.chdir(MakeDataDir())
2255 server = WebSocketServer(WebSocketOptions(host, port, MakeDataDir()))
2256 print 'WebSocket server started on %s:%d...' % (host, server.server_port)
2257 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002258 elif options.server_type == SERVER_SYNC:
2259 xmpp_port = options.xmpp_port
2260 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
2261 print 'Sync HTTP server started on port %d...' % server.server_port
2262 print 'Sync XMPP server started on port %d...' % server.xmpp_port
2263 server_data['port'] = server.server_port
2264 server_data['xmpp_port'] = server.xmpp_port
2265 elif options.server_type == SERVER_TCP_ECHO:
2266 # Used for generating the key (randomly) that encodes the "echo request"
2267 # message.
2268 random.seed()
2269 server = TCPEchoServer((host, port), TCPEchoHandler)
2270 print 'Echo TCP server started on port %d...' % server.server_port
2271 server_data['port'] = server.server_port
2272 elif options.server_type == SERVER_UDP_ECHO:
2273 # Used for generating the key (randomly) that encodes the "echo request"
2274 # message.
2275 random.seed()
2276 server = UDPEchoServer((host, port), UDPEchoHandler)
2277 print 'Echo UDP server started on port %d...' % server.server_port
2278 server_data['port'] = server.server_port
bashi@chromium.org33233532012-09-08 17:37:24 +00002279 elif options.server_type == SERVER_BASIC_AUTH_PROXY:
2280 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
2281 print 'BasicAuthProxy server started on port %d...' % server.server_port
2282 server_data['port'] = server.server_port
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002283 # means FTP Server
2284 else:
2285 my_data_dir = MakeDataDir()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002286
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002287 # Instantiate a dummy authorizer for managing 'virtual' users
2288 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002289
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002290 # Define a new user having full r/w permissions and a read-only
2291 # anonymous user
2292 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002293
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002294 authorizer.add_anonymous(my_data_dir)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002295
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002296 # Instantiate FTP handler class
2297 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2298 ftp_handler.authorizer = authorizer
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002299
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002300 # Define a customized banner (string returned when client connects)
2301 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2302 pyftpdlib.ftpserver.__ver__)
2303
2304 # Instantiate FTP server class and listen to address:port
2305 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2306 server_data['port'] = server.socket.getsockname()[1]
2307 print 'FTP server started on port %d...' % server_data['port']
2308
2309 # Notify the parent that we've started. (BaseServer subclasses
2310 # bind their sockets on construction.)
2311 if options.startup_pipe is not None:
2312 server_data_json = json.dumps(server_data)
2313 server_data_len = len(server_data_json)
2314 print 'sending server_data: %s (%d bytes)' % (
2315 server_data_json, server_data_len)
2316 if sys.platform == 'win32':
2317 fd = msvcrt.open_osfhandle(options.startup_pipe, 0)
2318 else:
2319 fd = options.startup_pipe
2320 startup_pipe = os.fdopen(fd, "w")
2321 # First write the data length as an unsigned 4-byte value. This
2322 # is _not_ using network byte ordering since the other end of the
2323 # pipe is on the same machine.
2324 startup_pipe.write(struct.pack('=L', server_data_len))
2325 startup_pipe.write(server_data_json)
2326 startup_pipe.close()
2327
2328 if ocsp_server is not None:
2329 ocsp_server.serve_forever_on_thread()
2330
2331 try:
2332 server.serve_forever()
2333 except KeyboardInterrupt:
2334 print 'shutting down server'
2335 if ocsp_server is not None:
2336 ocsp_server.stop_serving()
2337 server.stop = True
initial.commit94958cf2008-07-26 22:42:52 +00002338
2339if __name__ == '__main__':
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002340 option_parser = optparse.OptionParser()
2341 option_parser.add_option("-f", '--ftp', action='store_const',
2342 const=SERVER_FTP, default=SERVER_HTTP,
2343 dest='server_type',
2344 help='start up an FTP server.')
2345 option_parser.add_option('', '--sync', action='store_const',
2346 const=SERVER_SYNC, default=SERVER_HTTP,
2347 dest='server_type',
2348 help='start up a sync server.')
2349 option_parser.add_option('', '--tcp-echo', action='store_const',
2350 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2351 dest='server_type',
2352 help='start up a tcp echo server.')
2353 option_parser.add_option('', '--udp-echo', action='store_const',
2354 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2355 dest='server_type',
2356 help='start up a udp echo server.')
bashi@chromium.org33233532012-09-08 17:37:24 +00002357 option_parser.add_option('', '--basic-auth-proxy', action='store_const',
2358 const=SERVER_BASIC_AUTH_PROXY, default=SERVER_HTTP,
2359 dest='server_type',
2360 help='start up a proxy server which requires basic '
2361 'authentication.')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +00002362 option_parser.add_option('', '--websocket', action='store_const',
2363 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2364 dest='server_type',
2365 help='start up a WebSocket server.')
mattm@chromium.org6be1b5e2012-09-05 01:24:29 +00002366 option_parser.add_option('', '--log-to-console', action='store_const',
2367 const=True, default=False,
2368 dest='log_to_console',
2369 help='Enables or disables sys.stdout logging to '
2370 'the console.')
2371 option_parser.add_option('', '--port', default='0', type='int',
2372 help='Port used by the server. If unspecified, the '
2373 'server will listen on an ephemeral port.')
2374 option_parser.add_option('', '--xmpp-port', default='0', type='int',
2375 help='Port used by the XMPP server. If unspecified, '
2376 'the XMPP server will listen on an ephemeral port.')
2377 option_parser.add_option('', '--data-dir', dest='data_dir',
2378 help='Directory from which to read the files.')
2379 option_parser.add_option('', '--https', action='store_true', dest='https',
2380 help='Specify that https should be used.')
2381 option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
2382 help='specify the path to the file containing the '
2383 'certificate and private key for the server in PEM '
2384 'format')
2385 option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
2386 help='The type of OCSP response generated for the '
2387 'automatically generated certificate. One of '
2388 '[ok,revoked,invalid]')
2389 option_parser.add_option('', '--tls-intolerant', dest='tls_intolerant',
2390 default='0', type='int',
2391 help='If nonzero, certain TLS connections will be'
2392 ' aborted in order to test version fallback. 1'
2393 ' means all TLS versions will be aborted. 2 means'
2394 ' TLS 1.1 or higher will be aborted. 3 means TLS'
2395 ' 1.2 or higher will be aborted.')
2396 option_parser.add_option('', '--https-record-resume', dest='record_resume',
2397 const=True, default=False, action='store_const',
2398 help='Record resumption cache events rather than'
2399 ' resuming as normal. Allows the use of the'
2400 ' /ssl-session-cache request')
2401 option_parser.add_option('', '--ssl-client-auth', action='store_true',
2402 help='Require SSL client auth on every connection.')
2403 option_parser.add_option('', '--ssl-client-ca', action='append', default=[],
2404 help='Specify that the client certificate request '
2405 'should include the CA named in the subject of '
2406 'the DER-encoded certificate contained in the '
2407 'specified file. This option may appear multiple '
2408 'times, indicating multiple CA names should be '
2409 'sent in the request.')
2410 option_parser.add_option('', '--ssl-bulk-cipher', action='append',
2411 help='Specify the bulk encryption algorithm(s)'
2412 'that will be accepted by the SSL server. Valid '
2413 'values are "aes256", "aes128", "3des", "rc4". If '
2414 'omitted, all algorithms will be used. This '
2415 'option may appear multiple times, indicating '
2416 'multiple algorithms should be enabled.');
2417 option_parser.add_option('', '--file-root-url', default='/files/',
2418 help='Specify a root URL for files served.')
2419 option_parser.add_option('', '--startup-pipe', type='int',
2420 dest='startup_pipe',
2421 help='File handle of pipe to parent process')
2422 option_parser.add_option('', '--policy-key', action='append',
2423 dest='policy_keys',
2424 help='Specify a path to a PEM-encoded private key '
2425 'to use for policy signing. May be specified '
2426 'multiple times in order to load multipe keys into '
2427 'the server. If ther server has multiple keys, it '
2428 'will rotate through them in at each request a '
2429 'round-robin fashion. The server will generate a '
2430 'random key if none is specified on the command '
2431 'line.')
2432 option_parser.add_option('', '--policy-user', default='user@example.com',
2433 dest='policy_user',
2434 help='Specify the user name the server should '
2435 'report back to the client as the user owning the '
2436 'token used for making the policy request.')
2437 option_parser.add_option('', '--host', default='127.0.0.1',
2438 dest='host',
2439 help='Hostname or IP upon which the server will '
2440 'listen. Client connections will also only be '
2441 'allowed from this address.')
2442 option_parser.add_option('', '--auth-token', dest='auth_token',
2443 help='Specify the auth token which should be used'
2444 'in the authorization header for GData.')
2445 options, args = option_parser.parse_args()
2446
2447 sys.exit(main(options, args))