blob: 9babbb24eb43bc51c234a510a0cfdad93bdda8c0 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 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
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Temporary hack to deal with tlslite 0.3.8 -> 0.4.6 upgrade.
43#
44# TODO(davidben): Remove this when it has cycled through all the bots and
45# developer checkouts or when http://crbug.com/356276 is resolved.
46try:
47 os.remove(os.path.join(ROOT_DIR, 'third_party', 'tlslite',
48 'tlslite', 'utils', 'hmac.pyc'))
49except Exception:
50 pass
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000051
52# Append at the end of sys.path, it's fine to use the system library.
53sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000054
davidben@chromium.org7d53b542014-04-10 17:56:44 +000055# Insert at the beginning of the path, we want to use our copies of the library
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000056# unconditionally.
57sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000058sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
59
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000060import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000061from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000062# import manually
63mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000064
davidben@chromium.org7d53b542014-04-10 17:56:44 +000065import pyftpdlib.ftpserver
66
67import tlslite
68import tlslite.api
69
70import echo_message
71import testserver_base
72
maruel@chromium.org756cf982009-03-05 12:46:38 +000073SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000074SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000075SERVER_TCP_ECHO = 2
76SERVER_UDP_ECHO = 3
77SERVER_BASIC_AUTH_PROXY = 4
78SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079
80# Default request queue size for WebSocketServer.
81_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000082
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000083class WebSocketOptions:
84 """Holds options for WebSocketServer."""
85
86 def __init__(self, host, port, data_dir):
87 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
88 self.server_host = host
89 self.port = port
90 self.websock_handlers = data_dir
91 self.scan_dir = None
92 self.allow_handlers_outside_root_dir = False
93 self.websock_handlers_map_file = None
94 self.cgi_directories = []
95 self.is_executable_method = None
96 self.allow_draft75 = False
97 self.strict = True
98
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000099 self.use_tls = False
100 self.private_key = None
101 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +0000102 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000103 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +0000104 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000105 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000106 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000107
mattm@chromium.org830a3712012-11-07 23:00:07 +0000108
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000109class RecordingSSLSessionCache(object):
110 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
111 lookups and inserts in order to test session cache behaviours."""
112
113 def __init__(self):
114 self.log = []
115
116 def __getitem__(self, sessionID):
117 self.log.append(('lookup', sessionID))
118 raise KeyError()
119
120 def __setitem__(self, sessionID, session):
121 self.log.append(('insert', sessionID))
122
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000124class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
125 testserver_base.BrokenPipeHandlerMixIn,
126 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000127 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000128 verification."""
129
130 pass
131
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000132class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
133 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000134 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000135 """This is a specialization of HTTPServer that serves an
136 OCSP response"""
137
138 def serve_forever_on_thread(self):
139 self.thread = threading.Thread(target = self.serve_forever,
140 name = "OCSPServerThread")
141 self.thread.start()
142
143 def stop_serving(self):
144 self.shutdown()
145 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000146
mattm@chromium.org830a3712012-11-07 23:00:07 +0000147
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000149 testserver_base.ClientRestrictingServerMixIn,
150 testserver_base.BrokenPipeHandlerMixIn,
151 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000152 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000153 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000154
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000155 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000156 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000157 ssl_bulk_ciphers, ssl_key_exchanges, enable_npn,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000158 record_resume_info, tls_intolerant,
159 tls_intolerance_type, signed_cert_timestamps,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +0000160 fallback_scsv_enabled, ocsp_response, disable_session_cache):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000161 self.cert_chain = tlslite.api.X509CertChain()
162 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000163 # Force using only python implementation - otherwise behavior is different
164 # depending on whether m2crypto Python module is present (error is thrown
165 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
166 # the hood.
167 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
168 private=True,
169 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000170 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000171 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000172 self.ssl_client_cert_types = []
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000173 if enable_npn:
174 self.next_protos = ['http/1.1']
175 else:
176 self.next_protos = None
ekasper@google.com24aa8222013-11-28 13:43:26 +0000177 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000178 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000179 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000180
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000181 if ssl_client_auth:
182 for ca_file in ssl_client_cas:
183 s = open(ca_file).read()
184 x509 = tlslite.api.X509()
185 x509.parse(s)
186 self.ssl_client_cas.append(x509.subject)
187
188 for cert_type in ssl_client_cert_types:
189 self.ssl_client_cert_types.append({
190 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
191 "dss_sign": tlslite.api.ClientCertificateType.dss_sign,
192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
196 if ssl_bulk_ciphers is not None:
197 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000198 if ssl_key_exchanges is not None:
199 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000200 if tls_intolerant != 0:
201 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
202 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
initial.commit94958cf2008-07-26 22:42:52 +0000203
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +0000204
205 if disable_session_cache:
206 self.session_cache = None
207 elif record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000208 # If record_resume_info is true then we'll replace the session cache with
209 # an object that records the lookups and inserts that it sees.
210 self.session_cache = RecordingSSLSessionCache()
211 else:
212 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000213 testserver_base.StoppableHTTPServer.__init__(self,
214 server_address,
215 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000216
217 def handshake(self, tlsConnection):
218 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000219
initial.commit94958cf2008-07-26 22:42:52 +0000220 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000221 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000222 tlsConnection.handshakeServer(certChain=self.cert_chain,
223 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000224 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000225 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000226 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000227 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000228 reqCertTypes=self.ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000229 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000230 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000231 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000232 fallbackSCSV=self.fallback_scsv_enabled,
233 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000234 tlsConnection.ignoreAbruptClose = True
235 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000236 except tlslite.api.TLSAbruptCloseError:
237 # Ignore abrupt close.
238 return True
initial.commit94958cf2008-07-26 22:42:52 +0000239 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000240 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000241 return False
242
akalin@chromium.org154bb132010-11-12 02:20:27 +0000243
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000244class FTPServer(testserver_base.ClientRestrictingServerMixIn,
245 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000246 """This is a specialization of FTPServer that adds client verification."""
247
248 pass
249
250
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000251class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
252 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000253 """A TCP echo server that echoes back what it has received."""
254
255 def server_bind(self):
256 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000257
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000258 SocketServer.TCPServer.server_bind(self)
259 host, port = self.socket.getsockname()[:2]
260 self.server_name = socket.getfqdn(host)
261 self.server_port = port
262
263 def serve_forever(self):
264 self.stop = False
265 self.nonce_time = None
266 while not self.stop:
267 self.handle_request()
268 self.socket.close()
269
270
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000271class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
272 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000273 """A UDP echo server that echoes back what it has received."""
274
275 def server_bind(self):
276 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000277
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000278 SocketServer.UDPServer.server_bind(self)
279 host, port = self.socket.getsockname()[:2]
280 self.server_name = socket.getfqdn(host)
281 self.server_port = port
282
283 def serve_forever(self):
284 self.stop = False
285 self.nonce_time = None
286 while not self.stop:
287 self.handle_request()
288 self.socket.close()
289
290
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000291class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000292 # Class variables to allow for persistence state between page handler
293 # invocations
294 rst_limits = {}
295 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000296
297 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000298 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000299 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000300 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000301 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000302 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000303 self.NoCacheMaxAgeTimeHandler,
304 self.NoCacheTimeHandler,
305 self.CacheTimeHandler,
306 self.CacheExpiresHandler,
307 self.CacheProxyRevalidateHandler,
308 self.CachePrivateHandler,
309 self.CachePublicHandler,
310 self.CacheSMaxAgeHandler,
311 self.CacheMustRevalidateHandler,
312 self.CacheMustRevalidateMaxAgeHandler,
313 self.CacheNoStoreHandler,
314 self.CacheNoStoreMaxAgeHandler,
315 self.CacheNoTransformHandler,
316 self.DownloadHandler,
317 self.DownloadFinishHandler,
318 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000319 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000320 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000321 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000322 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000323 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000324 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000325 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000326 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.AuthBasicHandler,
328 self.AuthDigestHandler,
329 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000330 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000331 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000332 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.ServerRedirectHandler,
334 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000335 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000336 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000337 self.GetChannelID,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000338 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000339 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000341 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000343 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000344 self.PostOnlyFileHandler,
345 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000346 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000347 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000348 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000349 head_handlers = [
350 self.FileHandler,
351 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000352
maruel@google.come250a9b2009-03-10 17:39:46 +0000353 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000354 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000355 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000356 'gif': 'image/gif',
357 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000358 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000359 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000360 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000361 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000362 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000363 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000364 }
initial.commit94958cf2008-07-26 22:42:52 +0000365 self._default_mime_type = 'text/html'
366
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000367 testserver_base.BasePageHandler.__init__(self, request, client_address,
368 socket_server, connect_handlers,
369 get_handlers, head_handlers,
370 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000371
initial.commit94958cf2008-07-26 22:42:52 +0000372 def GetMIMETypeFromName(self, file_name):
373 """Returns the mime type for the specified file_name. So far it only looks
374 at the file extension."""
375
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000376 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000377 if len(extension) == 0:
378 # no extension.
379 return self._default_mime_type
380
ericroman@google.comc17ca532009-05-07 03:51:05 +0000381 # extension starts with a dot, so we need to remove it
382 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000383
initial.commit94958cf2008-07-26 22:42:52 +0000384 def NoCacheMaxAgeTimeHandler(self):
385 """This request handler yields a page with the title set to the current
386 system time, and no caching requested."""
387
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000388 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000389 return False
390
391 self.send_response(200)
392 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000393 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000394 self.end_headers()
395
maruel@google.come250a9b2009-03-10 17:39:46 +0000396 self.wfile.write('<html><head><title>%s</title></head></html>' %
397 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000398
399 return True
400
401 def NoCacheTimeHandler(self):
402 """This request handler yields a page with the title set to the current
403 system time, and no caching requested."""
404
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000405 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000406 return False
407
408 self.send_response(200)
409 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000410 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000411 self.end_headers()
412
maruel@google.come250a9b2009-03-10 17:39:46 +0000413 self.wfile.write('<html><head><title>%s</title></head></html>' %
414 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000415
416 return True
417
418 def CacheTimeHandler(self):
419 """This request handler yields a page with the title set to the current
420 system time, and allows caching for one minute."""
421
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000422 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000423 return False
424
425 self.send_response(200)
426 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000427 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000428 self.end_headers()
429
maruel@google.come250a9b2009-03-10 17:39:46 +0000430 self.wfile.write('<html><head><title>%s</title></head></html>' %
431 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000432
433 return True
434
435 def CacheExpiresHandler(self):
436 """This request handler yields a page with the title set to the current
437 system time, and set the page to expire on 1 Jan 2099."""
438
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000439 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000440 return False
441
442 self.send_response(200)
443 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000444 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000445 self.end_headers()
446
maruel@google.come250a9b2009-03-10 17:39:46 +0000447 self.wfile.write('<html><head><title>%s</title></head></html>' %
448 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000449
450 return True
451
452 def CacheProxyRevalidateHandler(self):
453 """This request handler yields a page with the title set to the current
454 system time, and allows caching for 60 seconds"""
455
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000456 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000457 return False
458
459 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000460 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000461 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
462 self.end_headers()
463
maruel@google.come250a9b2009-03-10 17:39:46 +0000464 self.wfile.write('<html><head><title>%s</title></head></html>' %
465 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000466
467 return True
468
469 def CachePrivateHandler(self):
470 """This request handler yields a page with the title set to the current
471 system time, and allows caching for 5 seconds."""
472
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000473 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000474 return False
475
476 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000477 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000478 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000479 self.end_headers()
480
maruel@google.come250a9b2009-03-10 17:39:46 +0000481 self.wfile.write('<html><head><title>%s</title></head></html>' %
482 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000483
484 return True
485
486 def CachePublicHandler(self):
487 """This request handler yields a page with the title set to the current
488 system time, and allows caching for 5 seconds."""
489
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000490 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000491 return False
492
493 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000494 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000495 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000496 self.end_headers()
497
maruel@google.come250a9b2009-03-10 17:39:46 +0000498 self.wfile.write('<html><head><title>%s</title></head></html>' %
499 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000500
501 return True
502
503 def CacheSMaxAgeHandler(self):
504 """This request handler yields a page with the title set to the current
505 system time, and does not allow for caching."""
506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000511 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000512 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
513 self.end_headers()
514
maruel@google.come250a9b2009-03-10 17:39:46 +0000515 self.wfile.write('<html><head><title>%s</title></head></html>' %
516 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000517
518 return True
519
520 def CacheMustRevalidateHandler(self):
521 """This request handler yields a page with the title set to the current
522 system time, and does not allow caching."""
523
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000524 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000525 return False
526
527 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000528 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000529 self.send_header('Cache-Control', 'must-revalidate')
530 self.end_headers()
531
maruel@google.come250a9b2009-03-10 17:39:46 +0000532 self.wfile.write('<html><head><title>%s</title></head></html>' %
533 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000534
535 return True
536
537 def CacheMustRevalidateMaxAgeHandler(self):
538 """This request handler yields a page with the title set to the current
539 system time, and does not allow caching event though max-age of 60
540 seconds is specified."""
541
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000542 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000543 return False
544
545 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000546 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000547 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
548 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
initial.commit94958cf2008-07-26 22:42:52 +0000555 def CacheNoStoreHandler(self):
556 """This request handler yields a page with the title set to the current
557 system time, and does not allow the page to be stored."""
558
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000559 if not self._ShouldHandleRequest("/cache/no-store"):
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', 'no-store')
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 CacheNoStoreMaxAgeHandler(self):
573 """This request handler yields a page with the title set to the current
574 system time, and does not allow the page to be stored even though max-age
575 of 60 seconds is specified."""
576
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000577 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000578 return False
579
580 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000581 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000582 self.send_header('Cache-Control', 'max-age=60, no-store')
583 self.end_headers()
584
maruel@google.come250a9b2009-03-10 17:39:46 +0000585 self.wfile.write('<html><head><title>%s</title></head></html>' %
586 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000587
588 return True
589
590
591 def CacheNoTransformHandler(self):
592 """This request handler yields a page with the title set to the current
593 system time, and does not allow the content to transformed during
594 user-agent caching"""
595
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000596 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000597 return False
598
599 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000600 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000601 self.send_header('Cache-Control', 'no-transform')
602 self.end_headers()
603
maruel@google.come250a9b2009-03-10 17:39:46 +0000604 self.wfile.write('<html><head><title>%s</title></head></html>' %
605 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000606
607 return True
608
609 def EchoHeader(self):
610 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000611
ananta@chromium.org219b2062009-10-23 16:09:41 +0000612 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000613
ananta@chromium.org56812d02011-04-07 17:52:05 +0000614 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000615 """This function echoes back the value of a specific request header while
616 allowing caching for 16 hours."""
617
ananta@chromium.org56812d02011-04-07 17:52:05 +0000618 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000619
620 def EchoHeaderHelper(self, echo_header):
621 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000622
ananta@chromium.org219b2062009-10-23 16:09:41 +0000623 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000624 return False
625
626 query_char = self.path.find('?')
627 if query_char != -1:
628 header_name = self.path[query_char+1:]
629
630 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000631 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000632 if echo_header == '/echoheadercache':
633 self.send_header('Cache-control', 'max-age=60000')
634 else:
635 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000636 # insert a vary header to properly indicate that the cachability of this
637 # request is subject to value of the request header being echoed.
638 if len(header_name) > 0:
639 self.send_header('Vary', header_name)
640 self.end_headers()
641
642 if len(header_name) > 0:
643 self.wfile.write(self.headers.getheader(header_name))
644
645 return True
646
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000647 def ReadRequestBody(self):
648 """This function reads the body of the current HTTP request, handling
649 both plain and chunked transfer encoded requests."""
650
651 if self.headers.getheader('transfer-encoding') != 'chunked':
652 length = int(self.headers.getheader('content-length'))
653 return self.rfile.read(length)
654
655 # Read the request body as chunks.
656 body = ""
657 while True:
658 line = self.rfile.readline()
659 length = int(line, 16)
660 if length == 0:
661 self.rfile.readline()
662 break
663 body += self.rfile.read(length)
664 self.rfile.read(2)
665 return body
666
initial.commit94958cf2008-07-26 22:42:52 +0000667 def EchoHandler(self):
668 """This handler just echoes back the payload of the request, for testing
669 form submission."""
670
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000671 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000672 return False
673
674 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000675 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000676 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000677 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000678 return True
679
680 def EchoTitleHandler(self):
681 """This handler is like Echo, but sets the page title to the request."""
682
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000683 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000684 return False
685
686 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000687 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000688 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000689 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000690 self.wfile.write('<html><head><title>')
691 self.wfile.write(request)
692 self.wfile.write('</title></head></html>')
693 return True
694
695 def EchoAllHandler(self):
696 """This handler yields a (more) human-readable page listing information
697 about the request header & contents."""
698
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000699 if not self._ShouldHandleRequest("/echoall"):
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.end_headers()
705 self.wfile.write('<html><head><style>'
706 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
707 '</style></head><body>'
708 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000709 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000710 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000711
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000712 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000713 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000714 params = cgi.parse_qs(qs, keep_blank_values=1)
715
716 for param in params:
717 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000718
719 self.wfile.write('</pre>')
720
721 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
722
723 self.wfile.write('</body></html>')
724 return True
725
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000726 def EchoMultipartPostHandler(self):
727 """This handler echoes received multipart post data as json format."""
728
729 if not (self._ShouldHandleRequest("/echomultipartpost") or
730 self._ShouldHandleRequest("/searchbyimage")):
731 return False
732
733 content_type, parameters = cgi.parse_header(
734 self.headers.getheader('content-type'))
735 if content_type == 'multipart/form-data':
736 post_multipart = cgi.parse_multipart(self.rfile, parameters)
737 elif content_type == 'application/x-www-form-urlencoded':
738 raise Exception('POST by application/x-www-form-urlencoded is '
739 'not implemented.')
740 else:
741 post_multipart = {}
742
743 # Since the data can be binary, we encode them by base64.
744 post_multipart_base64_encoded = {}
745 for field, values in post_multipart.items():
746 post_multipart_base64_encoded[field] = [base64.b64encode(value)
747 for value in values]
748
749 result = {'POST_multipart' : post_multipart_base64_encoded}
750
751 self.send_response(200)
752 self.send_header("Content-type", "text/plain")
753 self.end_headers()
754 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
755 return True
756
initial.commit94958cf2008-07-26 22:42:52 +0000757 def DownloadHandler(self):
758 """This handler sends a downloadable file with or without reporting
759 the size (6K)."""
760
761 if self.path.startswith("/download-unknown-size"):
762 send_length = False
763 elif self.path.startswith("/download-known-size"):
764 send_length = True
765 else:
766 return False
767
768 #
769 # The test which uses this functionality is attempting to send
770 # small chunks of data to the client. Use a fairly large buffer
771 # so that we'll fill chrome's IO buffer enough to force it to
772 # actually write the data.
773 # See also the comments in the client-side of this test in
774 # download_uitest.cc
775 #
776 size_chunk1 = 35*1024
777 size_chunk2 = 10*1024
778
779 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000780 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000781 self.send_header('Cache-Control', 'max-age=0')
782 if send_length:
783 self.send_header('Content-Length', size_chunk1 + size_chunk2)
784 self.end_headers()
785
786 # First chunk of data:
787 self.wfile.write("*" * size_chunk1)
788 self.wfile.flush()
789
790 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000791 self.server.wait_for_download = True
792 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000793 self.server.handle_request()
794
795 # Second chunk of data:
796 self.wfile.write("*" * size_chunk2)
797 return True
798
799 def DownloadFinishHandler(self):
800 """This handler just tells the server to finish the current download."""
801
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000802 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000803 return False
804
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000805 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000806 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000807 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000808 self.send_header('Cache-Control', 'max-age=0')
809 self.end_headers()
810 return True
811
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000812 def _ReplaceFileData(self, data, query_parameters):
813 """Replaces matching substrings in a file.
814
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000815 If the 'replace_text' URL query parameter is present, it is expected to be
816 of the form old_text:new_text, which indicates that any old_text strings in
817 the file are replaced with new_text. Multiple 'replace_text' parameters may
818 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000819
820 If the parameters are not present, |data| is returned.
821 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000822
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000823 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000824 replace_text_values = query_dict.get('replace_text', [])
825 for replace_text_value in replace_text_values:
826 replace_text_args = replace_text_value.split(':')
827 if len(replace_text_args) != 2:
828 raise ValueError(
829 'replace_text must be of form old_text:new_text. Actual value: %s' %
830 replace_text_value)
831 old_text_b64, new_text_b64 = replace_text_args
832 old_text = base64.urlsafe_b64decode(old_text_b64)
833 new_text = base64.urlsafe_b64decode(new_text_b64)
834 data = data.replace(old_text, new_text)
835 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000836
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000837 def ZipFileHandler(self):
838 """This handler sends the contents of the requested file in compressed form.
839 Can pass in a parameter that specifies that the content length be
840 C - the compressed size (OK),
841 U - the uncompressed size (Non-standard, but handled),
842 S - less than compressed (OK because we keep going),
843 M - larger than compressed but less than uncompressed (an error),
844 L - larger than uncompressed (an error)
845 Example: compressedfiles/Picture_1.doc?C
846 """
847
848 prefix = "/compressedfiles/"
849 if not self.path.startswith(prefix):
850 return False
851
852 # Consume a request body if present.
853 if self.command == 'POST' or self.command == 'PUT' :
854 self.ReadRequestBody()
855
856 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
857
858 if not query in ('C', 'U', 'S', 'M', 'L'):
859 return False
860
861 sub_path = url_path[len(prefix):]
862 entries = sub_path.split('/')
863 file_path = os.path.join(self.server.data_dir, *entries)
864 if os.path.isdir(file_path):
865 file_path = os.path.join(file_path, 'index.html')
866
867 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000868 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000869 self.send_error(404)
870 return True
871
872 f = open(file_path, "rb")
873 data = f.read()
874 uncompressed_len = len(data)
875 f.close()
876
877 # Compress the data.
878 data = zlib.compress(data)
879 compressed_len = len(data)
880
881 content_length = compressed_len
882 if query == 'U':
883 content_length = uncompressed_len
884 elif query == 'S':
885 content_length = compressed_len / 2
886 elif query == 'M':
887 content_length = (compressed_len + uncompressed_len) / 2
888 elif query == 'L':
889 content_length = compressed_len + uncompressed_len
890
891 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000892 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000893 self.send_header('Content-encoding', 'deflate')
894 self.send_header('Connection', 'close')
895 self.send_header('Content-Length', content_length)
896 self.send_header('ETag', '\'' + file_path + '\'')
897 self.end_headers()
898
899 self.wfile.write(data)
900
901 return True
902
initial.commit94958cf2008-07-26 22:42:52 +0000903 def FileHandler(self):
904 """This handler sends the contents of the requested file. Wow, it's like
905 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000906
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000907 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000908 if not self.path.startswith(prefix):
909 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000910 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000911
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000912 def PostOnlyFileHandler(self):
913 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000914
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000915 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000916 if not self.path.startswith(prefix):
917 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000918 return self._FileHandlerHelper(prefix)
919
920 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000921 request_body = ''
922 if self.command == 'POST' or self.command == 'PUT':
923 # Consume a request body if present.
924 request_body = self.ReadRequestBody()
925
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000926 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000927 query_dict = cgi.parse_qs(query)
928
929 expected_body = query_dict.get('expected_body', [])
930 if expected_body and request_body not in expected_body:
931 self.send_response(404)
932 self.end_headers()
933 self.wfile.write('')
934 return True
935
936 expected_headers = query_dict.get('expected_headers', [])
937 for expected_header in expected_headers:
938 header_name, expected_value = expected_header.split(':')
939 if self.headers.getheader(header_name) != expected_value:
940 self.send_response(404)
941 self.end_headers()
942 self.wfile.write('')
943 return True
944
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000945 sub_path = url_path[len(prefix):]
946 entries = sub_path.split('/')
947 file_path = os.path.join(self.server.data_dir, *entries)
948 if os.path.isdir(file_path):
949 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000950
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000951 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000952 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000953 self.send_error(404)
954 return True
955
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000956 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000957 data = f.read()
958 f.close()
959
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000960 data = self._ReplaceFileData(data, query)
961
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000962 old_protocol_version = self.protocol_version
963
initial.commit94958cf2008-07-26 22:42:52 +0000964 # If file.mock-http-headers exists, it contains the headers we
965 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000966 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000967 if os.path.isfile(headers_path):
968 f = open(headers_path, "r")
969
970 # "HTTP/1.1 200 OK"
971 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000972 http_major, http_minor, status_code = re.findall(
973 'HTTP/(\d+).(\d+) (\d+)', response)[0]
974 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000975 self.send_response(int(status_code))
976
977 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000978 header_values = re.findall('(\S+):\s*(.*)', line)
979 if len(header_values) > 0:
980 # "name: value"
981 name, value = header_values[0]
982 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000983 f.close()
984 else:
985 # Could be more generic once we support mime-type sniffing, but for
986 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000987
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000988 range_header = self.headers.get('Range')
989 if range_header and range_header.startswith('bytes='):
990 # Note this doesn't handle all valid byte range_header values (i.e.
991 # left open ended ones), just enough for what we needed so far.
992 range_header = range_header[6:].split('-')
993 start = int(range_header[0])
994 if range_header[1]:
995 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000996 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000997 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000998
999 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001000 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1001 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001002 self.send_header('Content-Range', content_range)
1003 data = data[start: end + 1]
1004 else:
1005 self.send_response(200)
1006
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001007 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001008 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001009 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001010 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001011 self.end_headers()
1012
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001013 if (self.command != 'HEAD'):
1014 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001015
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001016 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001017 return True
1018
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001019 def SetCookieHandler(self):
1020 """This handler just sets a cookie, for testing cookie handling."""
1021
1022 if not self._ShouldHandleRequest("/set-cookie"):
1023 return False
1024
1025 query_char = self.path.find('?')
1026 if query_char != -1:
1027 cookie_values = self.path[query_char + 1:].split('&')
1028 else:
1029 cookie_values = ("",)
1030 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001031 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001032 for cookie_value in cookie_values:
1033 self.send_header('Set-Cookie', '%s' % cookie_value)
1034 self.end_headers()
1035 for cookie_value in cookie_values:
1036 self.wfile.write('%s' % cookie_value)
1037 return True
1038
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001039 def SetManyCookiesHandler(self):
1040 """This handler just sets a given number of cookies, for testing handling
1041 of large numbers of cookies."""
1042
1043 if not self._ShouldHandleRequest("/set-many-cookies"):
1044 return False
1045
1046 query_char = self.path.find('?')
1047 if query_char != -1:
1048 num_cookies = int(self.path[query_char + 1:])
1049 else:
1050 num_cookies = 0
1051 self.send_response(200)
1052 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001053 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001054 self.send_header('Set-Cookie', 'a=')
1055 self.end_headers()
1056 self.wfile.write('%d cookies were sent' % num_cookies)
1057 return True
1058
mattm@chromium.org983fc462012-06-30 00:52:08 +00001059 def ExpectAndSetCookieHandler(self):
1060 """Expects some cookies to be sent, and if they are, sets more cookies.
1061
1062 The expect parameter specifies a required cookie. May be specified multiple
1063 times.
1064 The set parameter specifies a cookie to set if all required cookies are
1065 preset. May be specified multiple times.
1066 The data parameter specifies the response body data to be returned."""
1067
1068 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1069 return False
1070
1071 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1072 query_dict = cgi.parse_qs(query)
1073 cookies = set()
1074 if 'Cookie' in self.headers:
1075 cookie_header = self.headers.getheader('Cookie')
1076 cookies.update([s.strip() for s in cookie_header.split(';')])
1077 got_all_expected_cookies = True
1078 for expected_cookie in query_dict.get('expect', []):
1079 if expected_cookie not in cookies:
1080 got_all_expected_cookies = False
1081 self.send_response(200)
1082 self.send_header('Content-Type', 'text/html')
1083 if got_all_expected_cookies:
1084 for cookie_value in query_dict.get('set', []):
1085 self.send_header('Set-Cookie', '%s' % cookie_value)
1086 self.end_headers()
1087 for data_value in query_dict.get('data', []):
1088 self.wfile.write(data_value)
1089 return True
1090
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001091 def SetHeaderHandler(self):
1092 """This handler sets a response header. Parameters are in the
1093 key%3A%20value&key2%3A%20value2 format."""
1094
1095 if not self._ShouldHandleRequest("/set-header"):
1096 return False
1097
1098 query_char = self.path.find('?')
1099 if query_char != -1:
1100 headers_values = self.path[query_char + 1:].split('&')
1101 else:
1102 headers_values = ("",)
1103 self.send_response(200)
1104 self.send_header('Content-Type', 'text/html')
1105 for header_value in headers_values:
1106 header_value = urllib.unquote(header_value)
1107 (key, value) = header_value.split(': ', 1)
1108 self.send_header(key, value)
1109 self.end_headers()
1110 for header_value in headers_values:
1111 self.wfile.write('%s' % header_value)
1112 return True
1113
initial.commit94958cf2008-07-26 22:42:52 +00001114 def AuthBasicHandler(self):
1115 """This handler tests 'Basic' authentication. It just sends a page with
1116 title 'user/pass' if you succeed."""
1117
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001118 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001119 return False
1120
1121 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001122 expected_password = 'secret'
1123 realm = 'testrealm'
1124 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001125
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001126 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1127 query_params = cgi.parse_qs(query, True)
1128 if 'set-cookie-if-challenged' in query_params:
1129 set_cookie_if_challenged = True
1130 if 'password' in query_params:
1131 expected_password = query_params['password'][0]
1132 if 'realm' in query_params:
1133 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001134
initial.commit94958cf2008-07-26 22:42:52 +00001135 auth = self.headers.getheader('authorization')
1136 try:
1137 if not auth:
1138 raise Exception('no auth')
1139 b64str = re.findall(r'Basic (\S+)', auth)[0]
1140 userpass = base64.b64decode(b64str)
1141 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001142 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001143 raise Exception('wrong password')
1144 except Exception, e:
1145 # Authentication failed.
1146 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001147 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001148 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001149 if set_cookie_if_challenged:
1150 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001151 self.end_headers()
1152 self.wfile.write('<html><head>')
1153 self.wfile.write('<title>Denied: %s</title>' % e)
1154 self.wfile.write('</head><body>')
1155 self.wfile.write('auth=%s<p>' % auth)
1156 self.wfile.write('b64str=%s<p>' % b64str)
1157 self.wfile.write('username: %s<p>' % username)
1158 self.wfile.write('userpass: %s<p>' % userpass)
1159 self.wfile.write('password: %s<p>' % password)
1160 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1161 self.wfile.write('</body></html>')
1162 return True
1163
1164 # Authentication successful. (Return a cachable response to allow for
1165 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001166 old_protocol_version = self.protocol_version
1167 self.protocol_version = "HTTP/1.1"
1168
initial.commit94958cf2008-07-26 22:42:52 +00001169 if_none_match = self.headers.getheader('if-none-match')
1170 if if_none_match == "abc":
1171 self.send_response(304)
1172 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001173 elif url_path.endswith(".gif"):
1174 # Using chrome/test/data/google/logo.gif as the test image
1175 test_image_path = ['google', 'logo.gif']
1176 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1177 if not os.path.isfile(gif_path):
1178 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001179 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001180 return True
1181
1182 f = open(gif_path, "rb")
1183 data = f.read()
1184 f.close()
1185
1186 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001187 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001188 self.send_header('Cache-control', 'max-age=60000')
1189 self.send_header('Etag', 'abc')
1190 self.end_headers()
1191 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001192 else:
1193 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001194 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001195 self.send_header('Cache-control', 'max-age=60000')
1196 self.send_header('Etag', 'abc')
1197 self.end_headers()
1198 self.wfile.write('<html><head>')
1199 self.wfile.write('<title>%s/%s</title>' % (username, password))
1200 self.wfile.write('</head><body>')
1201 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001202 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001203 self.wfile.write('</body></html>')
1204
rvargas@google.com54453b72011-05-19 01:11:11 +00001205 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001206 return True
1207
tonyg@chromium.org75054202010-03-31 22:06:10 +00001208 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001209 """Returns a nonce that's stable per request path for the server's lifetime.
1210 This is a fake implementation. A real implementation would only use a given
1211 nonce a single time (hence the name n-once). However, for the purposes of
1212 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001213
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001214 Args:
1215 force_reset: Iff set, the nonce will be changed. Useful for testing the
1216 "stale" response.
1217 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001218
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001219 if force_reset or not self.server.nonce_time:
1220 self.server.nonce_time = time.time()
1221 return hashlib.md5('privatekey%s%d' %
1222 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001223
1224 def AuthDigestHandler(self):
1225 """This handler tests 'Digest' authentication.
1226
1227 It just sends a page with title 'user/pass' if you succeed.
1228
1229 A stale response is sent iff "stale" is present in the request path.
1230 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001231
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001232 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001233 return False
1234
tonyg@chromium.org75054202010-03-31 22:06:10 +00001235 stale = 'stale' in self.path
1236 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001237 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001238 password = 'secret'
1239 realm = 'testrealm'
1240
1241 auth = self.headers.getheader('authorization')
1242 pairs = {}
1243 try:
1244 if not auth:
1245 raise Exception('no auth')
1246 if not auth.startswith('Digest'):
1247 raise Exception('not digest')
1248 # Pull out all the name="value" pairs as a dictionary.
1249 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1250
1251 # Make sure it's all valid.
1252 if pairs['nonce'] != nonce:
1253 raise Exception('wrong nonce')
1254 if pairs['opaque'] != opaque:
1255 raise Exception('wrong opaque')
1256
1257 # Check the 'response' value and make sure it matches our magic hash.
1258 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001259 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001260 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001261 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001262 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001263 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001264 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1265 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001266 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001267
1268 if pairs['response'] != response:
1269 raise Exception('wrong password')
1270 except Exception, e:
1271 # Authentication failed.
1272 self.send_response(401)
1273 hdr = ('Digest '
1274 'realm="%s", '
1275 'domain="/", '
1276 'qop="auth", '
1277 'algorithm=MD5, '
1278 'nonce="%s", '
1279 'opaque="%s"') % (realm, nonce, opaque)
1280 if stale:
1281 hdr += ', stale="TRUE"'
1282 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001283 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001284 self.end_headers()
1285 self.wfile.write('<html><head>')
1286 self.wfile.write('<title>Denied: %s</title>' % e)
1287 self.wfile.write('</head><body>')
1288 self.wfile.write('auth=%s<p>' % auth)
1289 self.wfile.write('pairs=%s<p>' % pairs)
1290 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1291 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1292 self.wfile.write('</body></html>')
1293 return True
1294
1295 # Authentication successful.
1296 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001297 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001298 self.end_headers()
1299 self.wfile.write('<html><head>')
1300 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1301 self.wfile.write('</head><body>')
1302 self.wfile.write('auth=%s<p>' % auth)
1303 self.wfile.write('pairs=%s<p>' % pairs)
1304 self.wfile.write('</body></html>')
1305
1306 return True
1307
1308 def SlowServerHandler(self):
1309 """Wait for the user suggested time before responding. The syntax is
1310 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001311
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001312 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001313 return False
1314 query_char = self.path.find('?')
1315 wait_sec = 1.0
1316 if query_char >= 0:
1317 try:
1318 wait_sec = int(self.path[query_char + 1:])
1319 except ValueError:
1320 pass
1321 time.sleep(wait_sec)
1322 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001323 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001324 self.end_headers()
1325 self.wfile.write("waited %d seconds" % wait_sec)
1326 return True
1327
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001328 def ChunkedServerHandler(self):
1329 """Send chunked response. Allows to specify chunks parameters:
1330 - waitBeforeHeaders - ms to wait before sending headers
1331 - waitBetweenChunks - ms to wait between chunks
1332 - chunkSize - size of each chunk in bytes
1333 - chunksNumber - number of chunks
1334 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1335 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001336
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001337 if not self._ShouldHandleRequest("/chunked"):
1338 return False
1339 query_char = self.path.find('?')
1340 chunkedSettings = {'waitBeforeHeaders' : 0,
1341 'waitBetweenChunks' : 0,
1342 'chunkSize' : 5,
1343 'chunksNumber' : 5}
1344 if query_char >= 0:
1345 params = self.path[query_char + 1:].split('&')
1346 for param in params:
1347 keyValue = param.split('=')
1348 if len(keyValue) == 2:
1349 try:
1350 chunkedSettings[keyValue[0]] = int(keyValue[1])
1351 except ValueError:
1352 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001353 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001354 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1355 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001356 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001357 self.send_header('Connection', 'close')
1358 self.send_header('Transfer-Encoding', 'chunked')
1359 self.end_headers()
1360 # Chunked encoding: sending all chunks, then final zero-length chunk and
1361 # then final CRLF.
1362 for i in range(0, chunkedSettings['chunksNumber']):
1363 if i > 0:
1364 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1365 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001366 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001367 self.sendChunkHelp('')
1368 return True
1369
initial.commit94958cf2008-07-26 22:42:52 +00001370 def ContentTypeHandler(self):
1371 """Returns a string of html with the given content type. E.g.,
1372 /contenttype?text/css returns an html file with the Content-Type
1373 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001374
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001375 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001376 return False
1377 query_char = self.path.find('?')
1378 content_type = self.path[query_char + 1:].strip()
1379 if not content_type:
1380 content_type = 'text/html'
1381 self.send_response(200)
1382 self.send_header('Content-Type', content_type)
1383 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001384 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001385 return True
1386
creis@google.com2f4f6a42011-03-25 19:44:19 +00001387 def NoContentHandler(self):
1388 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001389
creis@google.com2f4f6a42011-03-25 19:44:19 +00001390 if not self._ShouldHandleRequest("/nocontent"):
1391 return False
1392 self.send_response(204)
1393 self.end_headers()
1394 return True
1395
initial.commit94958cf2008-07-26 22:42:52 +00001396 def ServerRedirectHandler(self):
1397 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001398 '/server-redirect?http://foo.bar/asdf' to redirect to
1399 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001400
1401 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001402 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001403 return False
1404
1405 query_char = self.path.find('?')
1406 if query_char < 0 or len(self.path) <= query_char + 1:
1407 self.sendRedirectHelp(test_name)
1408 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001409 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001410
1411 self.send_response(301) # moved permanently
1412 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001413 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001414 self.end_headers()
1415 self.wfile.write('<html><head>')
1416 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1417
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001418 return True
initial.commit94958cf2008-07-26 22:42:52 +00001419
1420 def ClientRedirectHandler(self):
1421 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001422 '/client-redirect?http://foo.bar/asdf' to redirect to
1423 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001424
1425 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001426 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001427 return False
1428
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001429 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001430 if query_char < 0 or len(self.path) <= query_char + 1:
1431 self.sendRedirectHelp(test_name)
1432 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001433 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001434
1435 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001436 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001437 self.end_headers()
1438 self.wfile.write('<html><head>')
1439 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1440 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1441
1442 return True
1443
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001444 def GetSSLSessionCacheHandler(self):
1445 """Send a reply containing a log of the session cache operations."""
1446
1447 if not self._ShouldHandleRequest('/ssl-session-cache'):
1448 return False
1449
1450 self.send_response(200)
1451 self.send_header('Content-Type', 'text/plain')
1452 self.end_headers()
1453 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001454 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001455 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001456 self.wfile.write('Pass --https-record-resume in order to use' +
1457 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001458 return True
1459
1460 for (action, sessionID) in log:
1461 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001462 return True
1463
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001464 def SSLManySmallRecords(self):
1465 """Sends a reply consisting of a variety of small writes. These will be
1466 translated into a series of small SSL records when used over an HTTPS
1467 server."""
1468
1469 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1470 return False
1471
1472 self.send_response(200)
1473 self.send_header('Content-Type', 'text/plain')
1474 self.end_headers()
1475
1476 # Write ~26K of data, in 1350 byte chunks
1477 for i in xrange(20):
1478 self.wfile.write('*' * 1350)
1479 self.wfile.flush()
1480 return True
1481
agl@chromium.org04700be2013-03-02 18:40:41 +00001482 def GetChannelID(self):
1483 """Send a reply containing the hashed ChannelID that the client provided."""
1484
1485 if not self._ShouldHandleRequest('/channel-id'):
1486 return False
1487
1488 self.send_response(200)
1489 self.send_header('Content-Type', 'text/plain')
1490 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001491 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001492 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1493 return True
1494
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001495 def CloseSocketHandler(self):
1496 """Closes the socket without sending anything."""
1497
1498 if not self._ShouldHandleRequest('/close-socket'):
1499 return False
1500
1501 self.wfile.close()
1502 return True
1503
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001504 def RangeResetHandler(self):
1505 """Send data broken up by connection resets every N (default 4K) bytes.
1506 Support range requests. If the data requested doesn't straddle a reset
1507 boundary, it will all be sent. Used for testing resuming downloads."""
1508
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001509 def DataForRange(start, end):
1510 """Data to be provided for a particular range of bytes."""
1511 # Offset and scale to avoid too obvious (and hence potentially
1512 # collidable) data.
1513 return ''.join([chr(y % 256)
1514 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1515
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001516 if not self._ShouldHandleRequest('/rangereset'):
1517 return False
1518
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001519 # HTTP/1.1 is required for ETag and range support.
1520 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001521 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1522
1523 # Defaults
1524 size = 8000
1525 # Note that the rst is sent just before sending the rst_boundary byte.
1526 rst_boundary = 4000
1527 respond_to_range = True
1528 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001529 rst_limit = -1
1530 token = 'DEFAULT'
1531 fail_precondition = 0
1532 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001533
1534 # Parse the query
1535 qdict = urlparse.parse_qs(query, True)
1536 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001537 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001538 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001539 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001540 if 'token' in qdict:
1541 # Identifying token for stateful tests.
1542 token = qdict['token'][0]
1543 if 'rst_limit' in qdict:
1544 # Max number of rsts for a given token.
1545 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001546 if 'bounce_range' in qdict:
1547 respond_to_range = False
1548 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001549 # Note that hold_for_signal will not work with null range requests;
1550 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001551 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001552 if 'no_verifiers' in qdict:
1553 send_verifiers = False
1554 if 'fail_precondition' in qdict:
1555 fail_precondition = int(qdict['fail_precondition'][0])
1556
1557 # Record already set information, or set it.
1558 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1559 if rst_limit != 0:
1560 TestPageHandler.rst_limits[token] -= 1
1561 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1562 token, fail_precondition)
1563 if fail_precondition != 0:
1564 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001565
1566 first_byte = 0
1567 last_byte = size - 1
1568
1569 # Does that define what we want to return, or do we need to apply
1570 # a range?
1571 range_response = False
1572 range_header = self.headers.getheader('range')
1573 if range_header and respond_to_range:
1574 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1575 if mo.group(1):
1576 first_byte = int(mo.group(1))
1577 if mo.group(2):
1578 last_byte = int(mo.group(2))
1579 if last_byte > size - 1:
1580 last_byte = size - 1
1581 range_response = True
1582 if last_byte < first_byte:
1583 return False
1584
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001585 if (fail_precondition and
1586 (self.headers.getheader('If-Modified-Since') or
1587 self.headers.getheader('If-Match'))):
1588 self.send_response(412)
1589 self.end_headers()
1590 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001591
1592 if range_response:
1593 self.send_response(206)
1594 self.send_header('Content-Range',
1595 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1596 else:
1597 self.send_response(200)
1598 self.send_header('Content-Type', 'application/octet-stream')
1599 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001600 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001601 # If fail_precondition is non-zero, then the ETag for each request will be
1602 # different.
1603 etag = "%s%d" % (token, fail_precondition)
1604 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001605 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001606 self.end_headers()
1607
1608 if hold_for_signal:
1609 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1610 # a single byte, the self.server.handle_request() below hangs
1611 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001612 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001613 first_byte = first_byte + 1
1614 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001615 self.server.wait_for_download = True
1616 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001617 self.server.handle_request()
1618
1619 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001620 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001621 # No RST has been requested in this range, so we don't need to
1622 # do anything fancy; just write the data and let the python
1623 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001624 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001625 self.wfile.flush()
1626 return True
1627
1628 # We're resetting the connection part way in; go to the RST
1629 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001630 # Because socket semantics do not guarantee that all the data will be
1631 # sent when using the linger semantics to hard close a socket,
1632 # we send the data and then wait for our peer to release us
1633 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001634 data = DataForRange(first_byte, possible_rst)
1635 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001636 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001637 self.server.wait_for_download = True
1638 while self.server.wait_for_download:
1639 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001640 l_onoff = 1 # Linger is active.
1641 l_linger = 0 # Seconds to linger for.
1642 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1643 struct.pack('ii', l_onoff, l_linger))
1644
1645 # Close all duplicates of the underlying socket to force the RST.
1646 self.wfile.close()
1647 self.rfile.close()
1648 self.connection.close()
1649
1650 return True
1651
initial.commit94958cf2008-07-26 22:42:52 +00001652 def DefaultResponseHandler(self):
1653 """This is the catch-all response handler for requests that aren't handled
1654 by one of the special handlers above.
1655 Note that we specify the content-length as without it the https connection
1656 is not closed properly (and the browser keeps expecting data)."""
1657
1658 contents = "Default response given for path: " + self.path
1659 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001660 self.send_header('Content-Type', 'text/html')
1661 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001662 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001663 if (self.command != 'HEAD'):
1664 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001665 return True
1666
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001667 def RedirectConnectHandler(self):
1668 """Sends a redirect to the CONNECT request for www.redirect.com. This
1669 response is not specified by the RFC, so the browser should not follow
1670 the redirect."""
1671
1672 if (self.path.find("www.redirect.com") < 0):
1673 return False
1674
1675 dest = "http://www.destination.com/foo.js"
1676
1677 self.send_response(302) # moved temporarily
1678 self.send_header('Location', dest)
1679 self.send_header('Connection', 'close')
1680 self.end_headers()
1681 return True
1682
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001683 def ServerAuthConnectHandler(self):
1684 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1685 response doesn't make sense because the proxy server cannot request
1686 server authentication."""
1687
1688 if (self.path.find("www.server-auth.com") < 0):
1689 return False
1690
1691 challenge = 'Basic realm="WallyWorld"'
1692
1693 self.send_response(401) # unauthorized
1694 self.send_header('WWW-Authenticate', challenge)
1695 self.send_header('Connection', 'close')
1696 self.end_headers()
1697 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001698
1699 def DefaultConnectResponseHandler(self):
1700 """This is the catch-all response handler for CONNECT requests that aren't
1701 handled by one of the special handlers above. Real Web servers respond
1702 with 400 to CONNECT requests."""
1703
1704 contents = "Your client has issued a malformed or illegal request."
1705 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001706 self.send_header('Content-Type', 'text/html')
1707 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001708 self.end_headers()
1709 self.wfile.write(contents)
1710 return True
1711
initial.commit94958cf2008-07-26 22:42:52 +00001712 # called by the redirect handling function when there is no parameter
1713 def sendRedirectHelp(self, redirect_name):
1714 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001715 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001716 self.end_headers()
1717 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1718 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1719 self.wfile.write('</body></html>')
1720
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001721 # called by chunked handling function
1722 def sendChunkHelp(self, chunk):
1723 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1724 self.wfile.write('%X\r\n' % len(chunk))
1725 self.wfile.write(chunk)
1726 self.wfile.write('\r\n')
1727
akalin@chromium.org154bb132010-11-12 02:20:27 +00001728
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001729class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001730 def __init__(self, request, client_address, socket_server):
1731 handlers = [self.OCSPResponse]
1732 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001733 testserver_base.BasePageHandler.__init__(self, request, client_address,
1734 socket_server, [], handlers, [],
1735 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001736
1737 def OCSPResponse(self):
1738 self.send_response(200)
1739 self.send_header('Content-Type', 'application/ocsp-response')
1740 self.send_header('Content-Length', str(len(self.ocsp_response)))
1741 self.end_headers()
1742
1743 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001744
mattm@chromium.org830a3712012-11-07 23:00:07 +00001745
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001746class TCPEchoHandler(SocketServer.BaseRequestHandler):
1747 """The RequestHandler class for TCP echo server.
1748
1749 It is instantiated once per connection to the server, and overrides the
1750 handle() method to implement communication to the client.
1751 """
1752
1753 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001754 """Handles the request from the client and constructs a response."""
1755
1756 data = self.request.recv(65536).strip()
1757 # Verify the "echo request" message received from the client. Send back
1758 # "echo response" message if "echo request" message is valid.
1759 try:
1760 return_data = echo_message.GetEchoResponseData(data)
1761 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001762 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001763 except ValueError:
1764 return
1765
1766 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001767
1768
1769class UDPEchoHandler(SocketServer.BaseRequestHandler):
1770 """The RequestHandler class for UDP echo server.
1771
1772 It is instantiated once per connection to the server, and overrides the
1773 handle() method to implement communication to the client.
1774 """
1775
1776 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001777 """Handles the request from the client and constructs a response."""
1778
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001779 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001780 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001781 # Verify the "echo request" message received from the client. Send back
1782 # "echo response" message if "echo request" message is valid.
1783 try:
1784 return_data = echo_message.GetEchoResponseData(data)
1785 if not return_data:
1786 return
1787 except ValueError:
1788 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001789 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001790
1791
bashi@chromium.org33233532012-09-08 17:37:24 +00001792class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1793 """A request handler that behaves as a proxy server which requires
1794 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1795 """
1796
1797 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1798
1799 def parse_request(self):
1800 """Overrides parse_request to check credential."""
1801
1802 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1803 return False
1804
1805 auth = self.headers.getheader('Proxy-Authorization')
1806 if auth != self._AUTH_CREDENTIAL:
1807 self.send_response(407)
1808 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1809 self.end_headers()
1810 return False
1811
1812 return True
1813
1814 def _start_read_write(self, sock):
1815 sock.setblocking(0)
1816 self.request.setblocking(0)
1817 rlist = [self.request, sock]
1818 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001819 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001820 if errors:
1821 self.send_response(500)
1822 self.end_headers()
1823 return
1824 for s in ready_sockets:
1825 received = s.recv(1024)
1826 if len(received) == 0:
1827 return
1828 if s == self.request:
1829 other = sock
1830 else:
1831 other = self.request
1832 other.send(received)
1833
1834 def _do_common_method(self):
1835 url = urlparse.urlparse(self.path)
1836 port = url.port
1837 if not port:
1838 if url.scheme == 'http':
1839 port = 80
1840 elif url.scheme == 'https':
1841 port = 443
1842 if not url.hostname or not port:
1843 self.send_response(400)
1844 self.end_headers()
1845 return
1846
1847 if len(url.path) == 0:
1848 path = '/'
1849 else:
1850 path = url.path
1851 if len(url.query) > 0:
1852 path = '%s?%s' % (url.path, url.query)
1853
1854 sock = None
1855 try:
1856 sock = socket.create_connection((url.hostname, port))
1857 sock.send('%s %s %s\r\n' % (
1858 self.command, path, self.protocol_version))
1859 for header in self.headers.headers:
1860 header = header.strip()
1861 if (header.lower().startswith('connection') or
1862 header.lower().startswith('proxy')):
1863 continue
1864 sock.send('%s\r\n' % header)
1865 sock.send('\r\n')
1866 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001867 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001868 self.send_response(500)
1869 self.end_headers()
1870 finally:
1871 if sock is not None:
1872 sock.close()
1873
1874 def do_CONNECT(self):
1875 try:
1876 pos = self.path.rfind(':')
1877 host = self.path[:pos]
1878 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001879 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001880 self.send_response(400)
1881 self.end_headers()
1882
1883 try:
1884 sock = socket.create_connection((host, port))
1885 self.send_response(200, 'Connection established')
1886 self.end_headers()
1887 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001888 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001889 self.send_response(500)
1890 self.end_headers()
1891 finally:
1892 sock.close()
1893
1894 def do_GET(self):
1895 self._do_common_method()
1896
1897 def do_HEAD(self):
1898 self._do_common_method()
1899
1900
mattm@chromium.org830a3712012-11-07 23:00:07 +00001901class ServerRunner(testserver_base.TestServerRunner):
1902 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001903
mattm@chromium.org830a3712012-11-07 23:00:07 +00001904 def __init__(self):
1905 super(ServerRunner, self).__init__()
1906 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001907
mattm@chromium.org830a3712012-11-07 23:00:07 +00001908 def __make_data_dir(self):
1909 if self.options.data_dir:
1910 if not os.path.isdir(self.options.data_dir):
1911 raise testserver_base.OptionError('specified data dir not found: ' +
1912 self.options.data_dir + ' exiting...')
1913 my_data_dir = self.options.data_dir
1914 else:
1915 # Create the default path to our data dir, relative to the exe dir.
1916 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1917 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001918
mattm@chromium.org830a3712012-11-07 23:00:07 +00001919 #TODO(ibrar): Must use Find* funtion defined in google\tools
1920 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001921
mattm@chromium.org830a3712012-11-07 23:00:07 +00001922 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001923
mattm@chromium.org830a3712012-11-07 23:00:07 +00001924 def create_server(self, server_data):
1925 port = self.options.port
1926 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001927
mattm@chromium.org830a3712012-11-07 23:00:07 +00001928 if self.options.server_type == SERVER_HTTP:
1929 if self.options.https:
1930 pem_cert_and_key = None
1931 if self.options.cert_and_key_file:
1932 if not os.path.isfile(self.options.cert_and_key_file):
1933 raise testserver_base.OptionError(
1934 'specified server cert file not found: ' +
1935 self.options.cert_and_key_file + ' exiting...')
1936 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001937 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001938 # generate a new certificate and run an OCSP server for it.
1939 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001940 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001941 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001942
mattm@chromium.org830a3712012-11-07 23:00:07 +00001943 ocsp_der = None
1944 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001945
mattm@chromium.org830a3712012-11-07 23:00:07 +00001946 if self.options.ocsp == 'ok':
1947 ocsp_state = minica.OCSP_STATE_GOOD
1948 elif self.options.ocsp == 'revoked':
1949 ocsp_state = minica.OCSP_STATE_REVOKED
1950 elif self.options.ocsp == 'invalid':
1951 ocsp_state = minica.OCSP_STATE_INVALID
1952 elif self.options.ocsp == 'unauthorized':
1953 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1954 elif self.options.ocsp == 'unknown':
1955 ocsp_state = minica.OCSP_STATE_UNKNOWN
1956 else:
1957 raise testserver_base.OptionError('unknown OCSP status: ' +
1958 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001959
mattm@chromium.org830a3712012-11-07 23:00:07 +00001960 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1961 subject = "127.0.0.1",
1962 ocsp_url = ("http://%s:%d/ocsp" %
1963 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001964 ocsp_state = ocsp_state,
1965 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001966
1967 self.__ocsp_server.ocsp_response = ocsp_der
1968
1969 for ca_cert in self.options.ssl_client_ca:
1970 if not os.path.isfile(ca_cert):
1971 raise testserver_base.OptionError(
1972 'specified trusted client CA file not found: ' + ca_cert +
1973 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001974
1975 stapled_ocsp_response = None
1976 if self.__ocsp_server and self.options.staple_ocsp_response:
1977 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1978
mattm@chromium.org830a3712012-11-07 23:00:07 +00001979 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1980 self.options.ssl_client_auth,
1981 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001982 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001984 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00001985 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001986 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001987 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001988 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001989 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001990 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001991 self.options.fallback_scsv,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00001992 stapled_ocsp_response,
1993 self.options.disable_session_cache)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001994 print 'HTTPS server started on https://%s:%d...' % \
1995 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001996 else:
1997 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001998 print 'HTTP server started on http://%s:%d...' % \
1999 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002000
2001 server.data_dir = self.__make_data_dir()
2002 server.file_root_url = self.options.file_root_url
2003 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004 elif self.options.server_type == SERVER_WEBSOCKET:
2005 # Launch pywebsocket via WebSocketServer.
2006 logger = logging.getLogger()
2007 logger.addHandler(logging.StreamHandler())
2008 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2009 # is required to work correctly. It should be fixed from pywebsocket side.
2010 os.chdir(self.__make_data_dir())
2011 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002012 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002013 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002014 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002015 websocket_options.use_tls = True
2016 websocket_options.private_key = self.options.cert_and_key_file
2017 websocket_options.certificate = self.options.cert_and_key_file
2018 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002019 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002020 websocket_options.tls_client_auth = True
2021 if len(self.options.ssl_client_ca) != 1:
2022 raise testserver_base.OptionError(
2023 'one trusted client CA file should be specified')
2024 if not os.path.isfile(self.options.ssl_client_ca[0]):
2025 raise testserver_base.OptionError(
2026 'specified trusted client CA file not found: ' +
2027 self.options.ssl_client_ca[0] + ' exiting...')
2028 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2029 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002030 print 'WebSocket server started on %s://%s:%d...' % \
2031 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002032 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002033 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 elif self.options.server_type == SERVER_TCP_ECHO:
2035 # Used for generating the key (randomly) that encodes the "echo request"
2036 # message.
2037 random.seed()
2038 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002039 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 server_data['port'] = server.server_port
2041 elif self.options.server_type == SERVER_UDP_ECHO:
2042 # Used for generating the key (randomly) that encodes the "echo request"
2043 # message.
2044 random.seed()
2045 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002046 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002047 server_data['port'] = server.server_port
2048 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2049 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002050 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002051 server_data['port'] = server.server_port
2052 elif self.options.server_type == SERVER_FTP:
2053 my_data_dir = self.__make_data_dir()
2054
2055 # Instantiate a dummy authorizer for managing 'virtual' users
2056 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2057
2058 # Define a new user having full r/w permissions and a read-only
2059 # anonymous user
2060 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2061
2062 authorizer.add_anonymous(my_data_dir)
2063
2064 # Instantiate FTP handler class
2065 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2066 ftp_handler.authorizer = authorizer
2067
2068 # Define a customized banner (string returned when client connects)
2069 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2070 pyftpdlib.ftpserver.__ver__)
2071
2072 # Instantiate FTP server class and listen to address:port
2073 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2074 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002075 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002076 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002077 raise testserver_base.OptionError('unknown server type' +
2078 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002079
mattm@chromium.org830a3712012-11-07 23:00:07 +00002080 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002081
mattm@chromium.org830a3712012-11-07 23:00:07 +00002082 def run_server(self):
2083 if self.__ocsp_server:
2084 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002085
mattm@chromium.org830a3712012-11-07 23:00:07 +00002086 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002087
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088 if self.__ocsp_server:
2089 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002090
mattm@chromium.org830a3712012-11-07 23:00:07 +00002091 def add_options(self):
2092 testserver_base.TestServerRunner.add_options(self)
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002093 self.option_parser.add_option('--disable-session-cache',
2094 action='store_true',
2095 dest='disable_session_cache',
2096 help='tells the server to disable the'
2097 'TLS session cache.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002098 self.option_parser.add_option('-f', '--ftp', action='store_const',
2099 const=SERVER_FTP, default=SERVER_HTTP,
2100 dest='server_type',
2101 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002102 self.option_parser.add_option('--tcp-echo', action='store_const',
2103 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2104 dest='server_type',
2105 help='start up a tcp echo server.')
2106 self.option_parser.add_option('--udp-echo', action='store_const',
2107 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2108 dest='server_type',
2109 help='start up a udp echo server.')
2110 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2111 const=SERVER_BASIC_AUTH_PROXY,
2112 default=SERVER_HTTP, dest='server_type',
2113 help='start up a proxy server which requires '
2114 'basic authentication.')
2115 self.option_parser.add_option('--websocket', action='store_const',
2116 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2117 dest='server_type',
2118 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002119 self.option_parser.add_option('--https', action='store_true',
2120 dest='https', help='Specify that https '
2121 'should be used.')
2122 self.option_parser.add_option('--cert-and-key-file',
2123 dest='cert_and_key_file', help='specify the '
2124 'path to the file containing the certificate '
2125 'and private key for the server in PEM '
2126 'format')
2127 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2128 help='The type of OCSP response generated '
2129 'for the automatically generated '
2130 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002131 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2132 default=0, type=int,
2133 help='If non-zero then the generated '
2134 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002135 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2136 default='0', type='int',
2137 help='If nonzero, certain TLS connections '
2138 'will be aborted in order to test version '
2139 'fallback. 1 means all TLS versions will be '
2140 'aborted. 2 means TLS 1.1 or higher will be '
2141 'aborted. 3 means TLS 1.2 or higher will be '
2142 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002143 self.option_parser.add_option('--tls-intolerance-type',
2144 dest='tls_intolerance_type',
2145 default="alert",
2146 help='Controls how the server reacts to a '
2147 'TLS version it is intolerant to. Valid '
2148 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002149 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2150 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002151 default='',
2152 help='Base64 encoded SCT list. If set, '
2153 'server will respond with a '
2154 'signed_certificate_timestamp TLS extension '
2155 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002156 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2157 default=False, const=True,
2158 action='store_const',
2159 help='If given, TLS_FALLBACK_SCSV support '
2160 'will be enabled. This causes the server to '
2161 'reject fallback connections from compatible '
2162 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002163 self.option_parser.add_option('--staple-ocsp-response',
2164 dest='staple_ocsp_response',
2165 default=False, action='store_true',
2166 help='If set, server will staple the OCSP '
2167 'response whenever OCSP is on and the client '
2168 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002169 self.option_parser.add_option('--https-record-resume',
2170 dest='record_resume', const=True,
2171 default=False, action='store_const',
2172 help='Record resumption cache events rather '
2173 'than resuming as normal. Allows the use of '
2174 'the /ssl-session-cache request')
2175 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2176 help='Require SSL client auth on every '
2177 'connection.')
2178 self.option_parser.add_option('--ssl-client-ca', action='append',
2179 default=[], help='Specify that the client '
2180 'certificate request should include the CA '
2181 'named in the subject of the DER-encoded '
2182 'certificate contained in the specified '
2183 'file. This option may appear multiple '
2184 'times, indicating multiple CA names should '
2185 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002186 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2187 default=[], help='Specify that the client '
2188 'certificate request should include the '
2189 'specified certificate_type value. This '
2190 'option may appear multiple times, '
2191 'indicating multiple values should be send '
2192 'in the request. Valid values are '
2193 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2194 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002195 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2196 help='Specify the bulk encryption '
2197 'algorithm(s) that will be accepted by the '
2198 'SSL server. Valid values are "aes256", '
2199 '"aes128", "3des", "rc4". If omitted, all '
2200 'algorithms will be used. This option may '
2201 'appear multiple times, indicating '
2202 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002203 self.option_parser.add_option('--ssl-key-exchange', action='append',
2204 help='Specify the key exchange algorithm(s)'
2205 'that will be accepted by the SSL server. '
2206 'Valid values are "rsa", "dhe_rsa". If '
2207 'omitted, all algorithms will be used. This '
2208 'option may appear multiple times, '
2209 'indicating multiple algorithms should be '
2210 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002211 # TODO(davidben): Add ALPN support to tlslite.
2212 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2213 default=False, const=True,
2214 action='store_const',
2215 help='Enable server support for the NPN '
2216 'extension. The server will advertise '
2217 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002218 self.option_parser.add_option('--file-root-url', default='/files/',
2219 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002220 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2221 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2222 dest='ws_basic_auth',
2223 help='Enable basic-auth for WebSocket')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002224
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002225
initial.commit94958cf2008-07-26 22:42:52 +00002226if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002227 sys.exit(ServerRunner().main())