blob: e66be970407812395c4918c0949218e938fe2d3c [file] [log] [blame]
Andrew Grieve9c2b31d2019-03-26 15:08:10 +00001#!/usr/bin/env vpython
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
Matt Menke3a293bd2021-08-13 20:34:43 +00006"""This is a simple HTTP/TCP/PROXY/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# Insert at the beginning of the path, we want to use our copies of the library
Robert Iannucci0e7ec952018-01-18 22:44:16 +000043# unconditionally (since they contain modifications from anything that might be
44# obtained from e.g. PyPi).
Keita Suzuki83e26f92020-03-06 09:42:48 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket3', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000046sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
47
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000048import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000049from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000050# import manually
51mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000052
davidben@chromium.org7d53b542014-04-10 17:56:44 +000053import tlslite
54import tlslite.api
55
davidben@chromium.org7d53b542014-04-10 17:56:44 +000056import testserver_base
57
maruel@chromium.org756cf982009-03-05 12:46:38 +000058SERVER_HTTP = 0
Matt Menke3a293bd2021-08-13 20:34:43 +000059SERVER_BASIC_AUTH_PROXY = 1
60SERVER_WEBSOCKET = 2
61SERVER_PROXY = 3
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000062
63# Default request queue size for WebSocketServer.
64_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000065
dadrian4ccf51c2016-07-20 15:36:58 -070066OCSP_STATES_NO_SINGLE_RESPONSE = {
67 minica.OCSP_STATE_INVALID_RESPONSE,
68 minica.OCSP_STATE_UNAUTHORIZED,
69 minica.OCSP_STATE_TRY_LATER,
70 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
71}
72
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000073class WebSocketOptions:
74 """Holds options for WebSocketServer."""
75
76 def __init__(self, host, port, data_dir):
77 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
78 self.server_host = host
79 self.port = port
80 self.websock_handlers = data_dir
81 self.scan_dir = None
82 self.allow_handlers_outside_root_dir = False
83 self.websock_handlers_map_file = None
84 self.cgi_directories = []
85 self.is_executable_method = None
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000086
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000087 self.use_tls = False
88 self.private_key = None
89 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000090 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000091 self.tls_client_ca = None
92 self.use_basic_auth = False
Keita Suzuki83e26f92020-03-06 09:42:48 +000093 self.basic_auth_credential = 'Basic ' + base64.b64encode(
94 'test:test').decode()
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000095
mattm@chromium.org830a3712012-11-07 23:00:07 +000096
agl@chromium.orgf9e66792011-12-12 22:22:19 +000097class RecordingSSLSessionCache(object):
98 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
99 lookups and inserts in order to test session cache behaviours."""
100
101 def __init__(self):
102 self.log = []
103
104 def __getitem__(self, sessionID):
105 self.log.append(('lookup', sessionID))
106 raise KeyError()
107
108 def __setitem__(self, sessionID, session):
109 self.log.append(('insert', sessionID))
110
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000111
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000112class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
113 testserver_base.BrokenPipeHandlerMixIn,
114 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000115 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000116 verification."""
117
118 pass
119
Adam Rice34b2e312018-04-06 16:48:30 +0000120class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
121 HTTPServer):
122 """This variant of HTTPServer creates a new thread for every connection. It
123 should only be used with handlers that are known to be threadsafe."""
124
125 pass
126
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000127class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
128 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000129 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000130 """This is a specialization of HTTPServer that serves an
131 OCSP response"""
132
133 def serve_forever_on_thread(self):
134 self.thread = threading.Thread(target = self.serve_forever,
135 name = "OCSPServerThread")
136 self.thread.start()
137
138 def stop_serving(self):
139 self.shutdown()
140 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000141
mattm@chromium.org830a3712012-11-07 23:00:07 +0000142
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000143class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000144 testserver_base.ClientRestrictingServerMixIn,
145 testserver_base.BrokenPipeHandlerMixIn,
146 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000147 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000149
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000150 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000151 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700152 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
153 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000154 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700155 fallback_scsv_enabled, ocsp_response,
David Benjaminf839f1c2018-10-16 06:01:29 +0000156 alert_after_handshake, disable_channel_id, disable_ems,
157 simulate_tls13_downgrade, simulate_tls12_downgrade,
158 tls_max_version):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000159 self.cert_chain = tlslite.api.X509CertChain()
160 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000161 # Force using only python implementation - otherwise behavior is different
162 # depending on whether m2crypto Python module is present (error is thrown
163 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
164 # the hood.
165 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
166 private=True,
167 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000168 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000169 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000170 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700171 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000172 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000173 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000174 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000175
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000176 if ssl_client_auth:
177 for ca_file in ssl_client_cas:
178 s = open(ca_file).read()
179 x509 = tlslite.api.X509()
180 x509.parse(s)
181 self.ssl_client_cas.append(x509.subject)
182
183 for cert_type in ssl_client_cert_types:
184 self.ssl_client_cert_types.append({
185 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000186 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
187 }[cert_type])
188
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000189 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800190 # Enable SSLv3 for testing purposes.
191 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000192 if ssl_bulk_ciphers is not None:
193 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000194 if ssl_key_exchanges is not None:
195 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000196 if tls_intolerant != 0:
197 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
198 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700199 if alert_after_handshake:
200 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700201 if disable_channel_id:
202 self.ssl_handshake_settings.enableChannelID = False
203 if disable_ems:
204 self.ssl_handshake_settings.enableExtendedMasterSecret = False
David Benjaminf839f1c2018-10-16 06:01:29 +0000205 if simulate_tls13_downgrade:
206 self.ssl_handshake_settings.simulateTLS13Downgrade = True
207 if simulate_tls12_downgrade:
208 self.ssl_handshake_settings.simulateTLS12Downgrade = True
209 if tls_max_version != 0:
210 self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
bnc5fb33bd2016-08-05 12:09:21 -0700211 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000212
rsleevi8146efa2015-03-16 12:31:24 -0700213 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000214 # If record_resume_info is true then we'll replace the session cache with
215 # an object that records the lookups and inserts that it sees.
216 self.session_cache = RecordingSSLSessionCache()
217 else:
218 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000219 testserver_base.StoppableHTTPServer.__init__(self,
220 server_address,
221 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000222
223 def handshake(self, tlsConnection):
224 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000225
initial.commit94958cf2008-07-26 22:42:52 +0000226 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000227 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000228 tlsConnection.handshakeServer(certChain=self.cert_chain,
229 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000230 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000231 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000232 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000233 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000234 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700235 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000236 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000237 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000238 fallbackSCSV=self.fallback_scsv_enabled,
239 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000240 tlsConnection.ignoreAbruptClose = True
241 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000242 except tlslite.api.TLSAbruptCloseError:
243 # Ignore abrupt close.
244 return True
initial.commit94958cf2008-07-26 22:42:52 +0000245 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000246 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000247 return False
248
akalin@chromium.org154bb132010-11-12 02:20:27 +0000249
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000250class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000251 # Class variables to allow for persistence state between page handler
252 # invocations
253 rst_limits = {}
254 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000255
256 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000257 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000258 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000259 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000260 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000261 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000262 self.NoCacheMaxAgeTimeHandler,
263 self.NoCacheTimeHandler,
264 self.CacheTimeHandler,
265 self.CacheExpiresHandler,
266 self.CacheProxyRevalidateHandler,
267 self.CachePrivateHandler,
268 self.CachePublicHandler,
269 self.CacheSMaxAgeHandler,
270 self.CacheMustRevalidateHandler,
271 self.CacheMustRevalidateMaxAgeHandler,
272 self.CacheNoStoreHandler,
273 self.CacheNoStoreMaxAgeHandler,
274 self.CacheNoTransformHandler,
275 self.DownloadHandler,
276 self.DownloadFinishHandler,
277 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000278 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000279 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000280 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000281 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000282 self.SetCookieHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000283 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000284 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000285 self.AuthBasicHandler,
286 self.AuthDigestHandler,
287 self.SlowServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000288 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000289 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700290 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000291 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000292 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000293 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000294 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700295 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700296 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000297 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000298 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000299 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000300 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000301 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000302 self.PostOnlyFileHandler,
303 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000304 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000305 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000306 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000307 head_handlers = [
308 self.FileHandler,
309 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000310
maruel@google.come250a9b2009-03-10 17:39:46 +0000311 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000312 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000313 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000314 'gif': 'image/gif',
315 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000316 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700317 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000318 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000319 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000320 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000321 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000322 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000323 }
initial.commit94958cf2008-07-26 22:42:52 +0000324 self._default_mime_type = 'text/html'
325
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000326 testserver_base.BasePageHandler.__init__(self, request, client_address,
327 socket_server, connect_handlers,
328 get_handlers, head_handlers,
329 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000330
initial.commit94958cf2008-07-26 22:42:52 +0000331 def GetMIMETypeFromName(self, file_name):
332 """Returns the mime type for the specified file_name. So far it only looks
333 at the file extension."""
334
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000335 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000336 if len(extension) == 0:
337 # no extension.
338 return self._default_mime_type
339
ericroman@google.comc17ca532009-05-07 03:51:05 +0000340 # extension starts with a dot, so we need to remove it
341 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000342
initial.commit94958cf2008-07-26 22:42:52 +0000343 def NoCacheMaxAgeTimeHandler(self):
344 """This request handler yields a page with the title set to the current
345 system time, and no caching requested."""
346
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000347 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000348 return False
349
350 self.send_response(200)
351 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000352 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000353 self.end_headers()
354
maruel@google.come250a9b2009-03-10 17:39:46 +0000355 self.wfile.write('<html><head><title>%s</title></head></html>' %
356 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000357
358 return True
359
360 def NoCacheTimeHandler(self):
361 """This request handler yields a page with the title set to the current
362 system time, and no caching requested."""
363
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000364 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000365 return False
366
367 self.send_response(200)
368 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000369 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000370 self.end_headers()
371
maruel@google.come250a9b2009-03-10 17:39:46 +0000372 self.wfile.write('<html><head><title>%s</title></head></html>' %
373 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000374
375 return True
376
377 def CacheTimeHandler(self):
378 """This request handler yields a page with the title set to the current
379 system time, and allows caching for one minute."""
380
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000381 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000382 return False
383
384 self.send_response(200)
385 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000386 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000387 self.end_headers()
388
maruel@google.come250a9b2009-03-10 17:39:46 +0000389 self.wfile.write('<html><head><title>%s</title></head></html>' %
390 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000391
392 return True
393
394 def CacheExpiresHandler(self):
395 """This request handler yields a page with the title set to the current
396 system time, and set the page to expire on 1 Jan 2099."""
397
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000398 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000399 return False
400
401 self.send_response(200)
402 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000403 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000404 self.end_headers()
405
maruel@google.come250a9b2009-03-10 17:39:46 +0000406 self.wfile.write('<html><head><title>%s</title></head></html>' %
407 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000408
409 return True
410
411 def CacheProxyRevalidateHandler(self):
412 """This request handler yields a page with the title set to the current
413 system time, and allows caching for 60 seconds"""
414
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000415 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000416 return False
417
418 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000419 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000420 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
421 self.end_headers()
422
maruel@google.come250a9b2009-03-10 17:39:46 +0000423 self.wfile.write('<html><head><title>%s</title></head></html>' %
424 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000425
426 return True
427
428 def CachePrivateHandler(self):
429 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700430 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000431
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000432 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000433 return False
434
435 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000436 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000437 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000438 self.end_headers()
439
maruel@google.come250a9b2009-03-10 17:39:46 +0000440 self.wfile.write('<html><head><title>%s</title></head></html>' %
441 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000442
443 return True
444
445 def CachePublicHandler(self):
446 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700447 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000448
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000449 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000450 return False
451
452 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000453 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000454 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000455 self.end_headers()
456
maruel@google.come250a9b2009-03-10 17:39:46 +0000457 self.wfile.write('<html><head><title>%s</title></head></html>' %
458 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000459
460 return True
461
462 def CacheSMaxAgeHandler(self):
463 """This request handler yields a page with the title set to the current
464 system time, and does not allow for caching."""
465
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000466 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000467 return False
468
469 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000470 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000471 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
472 self.end_headers()
473
maruel@google.come250a9b2009-03-10 17:39:46 +0000474 self.wfile.write('<html><head><title>%s</title></head></html>' %
475 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000476
477 return True
478
479 def CacheMustRevalidateHandler(self):
480 """This request handler yields a page with the title set to the current
481 system time, and does not allow caching."""
482
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000483 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000484 return False
485
486 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000487 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000488 self.send_header('Cache-Control', 'must-revalidate')
489 self.end_headers()
490
maruel@google.come250a9b2009-03-10 17:39:46 +0000491 self.wfile.write('<html><head><title>%s</title></head></html>' %
492 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000493
494 return True
495
496 def CacheMustRevalidateMaxAgeHandler(self):
497 """This request handler yields a page with the title set to the current
498 system time, and does not allow caching event though max-age of 60
499 seconds is specified."""
500
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000501 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000502 return False
503
504 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000505 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000506 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
507 self.end_headers()
508
maruel@google.come250a9b2009-03-10 17:39:46 +0000509 self.wfile.write('<html><head><title>%s</title></head></html>' %
510 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000511
512 return True
513
initial.commit94958cf2008-07-26 22:42:52 +0000514 def CacheNoStoreHandler(self):
515 """This request handler yields a page with the title set to the current
516 system time, and does not allow the page to be stored."""
517
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000518 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000519 return False
520
521 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000522 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000523 self.send_header('Cache-Control', 'no-store')
524 self.end_headers()
525
maruel@google.come250a9b2009-03-10 17:39:46 +0000526 self.wfile.write('<html><head><title>%s</title></head></html>' %
527 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000528
529 return True
530
531 def CacheNoStoreMaxAgeHandler(self):
532 """This request handler yields a page with the title set to the current
533 system time, and does not allow the page to be stored even though max-age
534 of 60 seconds is specified."""
535
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000536 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000537 return False
538
539 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000540 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000541 self.send_header('Cache-Control', 'max-age=60, no-store')
542 self.end_headers()
543
maruel@google.come250a9b2009-03-10 17:39:46 +0000544 self.wfile.write('<html><head><title>%s</title></head></html>' %
545 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000546
547 return True
548
549
550 def CacheNoTransformHandler(self):
551 """This request handler yields a page with the title set to the current
552 system time, and does not allow the content to transformed during
553 user-agent caching"""
554
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000555 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000556 return False
557
558 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000559 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000560 self.send_header('Cache-Control', 'no-transform')
561 self.end_headers()
562
maruel@google.come250a9b2009-03-10 17:39:46 +0000563 self.wfile.write('<html><head><title>%s</title></head></html>' %
564 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000565
566 return True
567
568 def EchoHeader(self):
569 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000570
ananta@chromium.org219b2062009-10-23 16:09:41 +0000571 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000572
ananta@chromium.org56812d02011-04-07 17:52:05 +0000573 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000574 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000575 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000576
ananta@chromium.org56812d02011-04-07 17:52:05 +0000577 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000578
579 def EchoHeaderHelper(self, echo_header):
580 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000581
ananta@chromium.org219b2062009-10-23 16:09:41 +0000582 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000583 return False
584
585 query_char = self.path.find('?')
586 if query_char != -1:
587 header_name = self.path[query_char+1:]
588
589 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000590 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000591 if echo_header == '/echoheadercache':
592 self.send_header('Cache-control', 'max-age=60000')
593 else:
594 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000595 # insert a vary header to properly indicate that the cachability of this
596 # request is subject to value of the request header being echoed.
597 if len(header_name) > 0:
598 self.send_header('Vary', header_name)
599 self.end_headers()
600
601 if len(header_name) > 0:
602 self.wfile.write(self.headers.getheader(header_name))
603
604 return True
605
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000606 def ReadRequestBody(self):
607 """This function reads the body of the current HTTP request, handling
608 both plain and chunked transfer encoded requests."""
609
610 if self.headers.getheader('transfer-encoding') != 'chunked':
611 length = int(self.headers.getheader('content-length'))
612 return self.rfile.read(length)
613
614 # Read the request body as chunks.
615 body = ""
616 while True:
617 line = self.rfile.readline()
618 length = int(line, 16)
619 if length == 0:
620 self.rfile.readline()
621 break
622 body += self.rfile.read(length)
623 self.rfile.read(2)
624 return body
625
initial.commit94958cf2008-07-26 22:42:52 +0000626 def EchoHandler(self):
627 """This handler just echoes back the payload of the request, for testing
628 form submission."""
629
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000630 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000631 return False
632
hirono2838c572015-01-21 12:18:11 -0800633 _, _, _, _, query, _ = urlparse.urlparse(self.path)
634 query_params = cgi.parse_qs(query, True)
635 if 'status' in query_params:
636 self.send_response(int(query_params['status'][0]))
637 else:
638 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000639 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000640 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000641 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000642 return True
643
644 def EchoTitleHandler(self):
645 """This handler is like Echo, but sets the page title to the request."""
646
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000647 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000648 return False
649
650 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000651 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000652 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000653 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000654 self.wfile.write('<html><head><title>')
655 self.wfile.write(request)
656 self.wfile.write('</title></head></html>')
657 return True
658
659 def EchoAllHandler(self):
660 """This handler yields a (more) human-readable page listing information
661 about the request header & contents."""
662
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000663 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000664 return False
665
666 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000667 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000668 self.end_headers()
669 self.wfile.write('<html><head><style>'
670 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
671 '</style></head><body>'
672 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000673 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000674 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000675
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000676 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000677 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000678 params = cgi.parse_qs(qs, keep_blank_values=1)
679
680 for param in params:
681 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000682
683 self.wfile.write('</pre>')
684
685 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
686
687 self.wfile.write('</body></html>')
688 return True
689
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000690 def EchoMultipartPostHandler(self):
691 """This handler echoes received multipart post data as json format."""
692
693 if not (self._ShouldHandleRequest("/echomultipartpost") or
694 self._ShouldHandleRequest("/searchbyimage")):
695 return False
696
697 content_type, parameters = cgi.parse_header(
698 self.headers.getheader('content-type'))
699 if content_type == 'multipart/form-data':
700 post_multipart = cgi.parse_multipart(self.rfile, parameters)
701 elif content_type == 'application/x-www-form-urlencoded':
702 raise Exception('POST by application/x-www-form-urlencoded is '
703 'not implemented.')
704 else:
705 post_multipart = {}
706
707 # Since the data can be binary, we encode them by base64.
708 post_multipart_base64_encoded = {}
709 for field, values in post_multipart.items():
710 post_multipart_base64_encoded[field] = [base64.b64encode(value)
711 for value in values]
712
713 result = {'POST_multipart' : post_multipart_base64_encoded}
714
715 self.send_response(200)
716 self.send_header("Content-type", "text/plain")
717 self.end_headers()
718 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
719 return True
720
initial.commit94958cf2008-07-26 22:42:52 +0000721 def DownloadHandler(self):
722 """This handler sends a downloadable file with or without reporting
723 the size (6K)."""
724
725 if self.path.startswith("/download-unknown-size"):
726 send_length = False
727 elif self.path.startswith("/download-known-size"):
728 send_length = True
729 else:
730 return False
731
732 #
733 # The test which uses this functionality is attempting to send
734 # small chunks of data to the client. Use a fairly large buffer
735 # so that we'll fill chrome's IO buffer enough to force it to
736 # actually write the data.
737 # See also the comments in the client-side of this test in
738 # download_uitest.cc
739 #
740 size_chunk1 = 35*1024
741 size_chunk2 = 10*1024
742
743 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000744 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000745 self.send_header('Cache-Control', 'max-age=0')
746 if send_length:
747 self.send_header('Content-Length', size_chunk1 + size_chunk2)
748 self.end_headers()
749
750 # First chunk of data:
751 self.wfile.write("*" * size_chunk1)
752 self.wfile.flush()
753
754 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000755 self.server.wait_for_download = True
756 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000757 self.server.handle_request()
758
759 # Second chunk of data:
760 self.wfile.write("*" * size_chunk2)
761 return True
762
763 def DownloadFinishHandler(self):
764 """This handler just tells the server to finish the current download."""
765
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000766 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000767 return False
768
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000769 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000770 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000771 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000772 self.send_header('Cache-Control', 'max-age=0')
773 self.end_headers()
774 return True
775
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000776 def _ReplaceFileData(self, data, query_parameters):
777 """Replaces matching substrings in a file.
778
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000779 If the 'replace_text' URL query parameter is present, it is expected to be
780 of the form old_text:new_text, which indicates that any old_text strings in
781 the file are replaced with new_text. Multiple 'replace_text' parameters may
782 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000783
784 If the parameters are not present, |data| is returned.
785 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000786
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000787 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000788 replace_text_values = query_dict.get('replace_text', [])
789 for replace_text_value in replace_text_values:
790 replace_text_args = replace_text_value.split(':')
791 if len(replace_text_args) != 2:
792 raise ValueError(
793 'replace_text must be of form old_text:new_text. Actual value: %s' %
794 replace_text_value)
795 old_text_b64, new_text_b64 = replace_text_args
796 old_text = base64.urlsafe_b64decode(old_text_b64)
797 new_text = base64.urlsafe_b64decode(new_text_b64)
798 data = data.replace(old_text, new_text)
799 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000800
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000801 def ZipFileHandler(self):
802 """This handler sends the contents of the requested file in compressed form.
803 Can pass in a parameter that specifies that the content length be
804 C - the compressed size (OK),
805 U - the uncompressed size (Non-standard, but handled),
806 S - less than compressed (OK because we keep going),
807 M - larger than compressed but less than uncompressed (an error),
808 L - larger than uncompressed (an error)
809 Example: compressedfiles/Picture_1.doc?C
810 """
811
812 prefix = "/compressedfiles/"
813 if not self.path.startswith(prefix):
814 return False
815
816 # Consume a request body if present.
817 if self.command == 'POST' or self.command == 'PUT' :
818 self.ReadRequestBody()
819
820 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
821
822 if not query in ('C', 'U', 'S', 'M', 'L'):
823 return False
824
825 sub_path = url_path[len(prefix):]
826 entries = sub_path.split('/')
827 file_path = os.path.join(self.server.data_dir, *entries)
828 if os.path.isdir(file_path):
829 file_path = os.path.join(file_path, 'index.html')
830
831 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000832 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000833 self.send_error(404)
834 return True
835
836 f = open(file_path, "rb")
837 data = f.read()
838 uncompressed_len = len(data)
839 f.close()
840
841 # Compress the data.
842 data = zlib.compress(data)
843 compressed_len = len(data)
844
845 content_length = compressed_len
846 if query == 'U':
847 content_length = uncompressed_len
848 elif query == 'S':
849 content_length = compressed_len / 2
850 elif query == 'M':
851 content_length = (compressed_len + uncompressed_len) / 2
852 elif query == 'L':
853 content_length = compressed_len + uncompressed_len
854
855 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000856 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000857 self.send_header('Content-encoding', 'deflate')
858 self.send_header('Connection', 'close')
859 self.send_header('Content-Length', content_length)
860 self.send_header('ETag', '\'' + file_path + '\'')
861 self.end_headers()
862
863 self.wfile.write(data)
864
865 return True
866
initial.commit94958cf2008-07-26 22:42:52 +0000867 def FileHandler(self):
868 """This handler sends the contents of the requested file. Wow, it's like
869 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000870
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000871 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000872 if not self.path.startswith(prefix):
873 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000874 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000875
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000876 def PostOnlyFileHandler(self):
877 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000878
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000879 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000880 if not self.path.startswith(prefix):
881 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000882 return self._FileHandlerHelper(prefix)
883
884 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000885 request_body = ''
886 if self.command == 'POST' or self.command == 'PUT':
887 # Consume a request body if present.
888 request_body = self.ReadRequestBody()
889
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000890 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000891 query_dict = cgi.parse_qs(query)
892
893 expected_body = query_dict.get('expected_body', [])
894 if expected_body and request_body not in expected_body:
895 self.send_response(404)
896 self.end_headers()
897 self.wfile.write('')
898 return True
899
900 expected_headers = query_dict.get('expected_headers', [])
901 for expected_header in expected_headers:
902 header_name, expected_value = expected_header.split(':')
903 if self.headers.getheader(header_name) != expected_value:
904 self.send_response(404)
905 self.end_headers()
906 self.wfile.write('')
907 return True
908
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000909 sub_path = url_path[len(prefix):]
910 entries = sub_path.split('/')
911 file_path = os.path.join(self.server.data_dir, *entries)
912 if os.path.isdir(file_path):
913 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000914
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000915 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000916 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000917 self.send_error(404)
918 return True
919
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000920 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000921 data = f.read()
922 f.close()
923
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000924 data = self._ReplaceFileData(data, query)
925
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000926 old_protocol_version = self.protocol_version
927
initial.commit94958cf2008-07-26 22:42:52 +0000928 # If file.mock-http-headers exists, it contains the headers we
929 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000930 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000931 if os.path.isfile(headers_path):
932 f = open(headers_path, "r")
933
934 # "HTTP/1.1 200 OK"
935 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000936 http_major, http_minor, status_code = re.findall(
937 'HTTP/(\d+).(\d+) (\d+)', response)[0]
938 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000939 self.send_response(int(status_code))
940
941 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000942 header_values = re.findall('(\S+):\s*(.*)', line)
943 if len(header_values) > 0:
944 # "name: value"
945 name, value = header_values[0]
946 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000947 f.close()
948 else:
949 # Could be more generic once we support mime-type sniffing, but for
950 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000951
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000952 range_header = self.headers.get('Range')
953 if range_header and range_header.startswith('bytes='):
954 # Note this doesn't handle all valid byte range_header values (i.e.
955 # left open ended ones), just enough for what we needed so far.
956 range_header = range_header[6:].split('-')
957 start = int(range_header[0])
958 if range_header[1]:
959 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000960 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000961 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000962
963 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000964 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
965 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000966 self.send_header('Content-Range', content_range)
967 data = data[start: end + 1]
968 else:
969 self.send_response(200)
970
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000971 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000972 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000973 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000974 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000975 self.end_headers()
976
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000977 if (self.command != 'HEAD'):
978 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000979
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000980 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000981 return True
982
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000983 def SetCookieHandler(self):
984 """This handler just sets a cookie, for testing cookie handling."""
985
986 if not self._ShouldHandleRequest("/set-cookie"):
987 return False
988
989 query_char = self.path.find('?')
990 if query_char != -1:
991 cookie_values = self.path[query_char + 1:].split('&')
992 else:
993 cookie_values = ("",)
994 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000995 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000996 for cookie_value in cookie_values:
997 self.send_header('Set-Cookie', '%s' % cookie_value)
998 self.end_headers()
999 for cookie_value in cookie_values:
1000 self.wfile.write('%s' % cookie_value)
1001 return True
1002
mattm@chromium.org983fc462012-06-30 00:52:08 +00001003 def ExpectAndSetCookieHandler(self):
1004 """Expects some cookies to be sent, and if they are, sets more cookies.
1005
1006 The expect parameter specifies a required cookie. May be specified multiple
1007 times.
1008 The set parameter specifies a cookie to set if all required cookies are
1009 preset. May be specified multiple times.
1010 The data parameter specifies the response body data to be returned."""
1011
1012 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1013 return False
1014
1015 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1016 query_dict = cgi.parse_qs(query)
1017 cookies = set()
1018 if 'Cookie' in self.headers:
1019 cookie_header = self.headers.getheader('Cookie')
1020 cookies.update([s.strip() for s in cookie_header.split(';')])
1021 got_all_expected_cookies = True
1022 for expected_cookie in query_dict.get('expect', []):
1023 if expected_cookie not in cookies:
1024 got_all_expected_cookies = False
1025 self.send_response(200)
1026 self.send_header('Content-Type', 'text/html')
1027 if got_all_expected_cookies:
1028 for cookie_value in query_dict.get('set', []):
1029 self.send_header('Set-Cookie', '%s' % cookie_value)
1030 self.end_headers()
1031 for data_value in query_dict.get('data', []):
1032 self.wfile.write(data_value)
1033 return True
1034
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001035 def SetHeaderHandler(self):
1036 """This handler sets a response header. Parameters are in the
1037 key%3A%20value&key2%3A%20value2 format."""
1038
1039 if not self._ShouldHandleRequest("/set-header"):
1040 return False
1041
1042 query_char = self.path.find('?')
1043 if query_char != -1:
1044 headers_values = self.path[query_char + 1:].split('&')
1045 else:
1046 headers_values = ("",)
1047 self.send_response(200)
1048 self.send_header('Content-Type', 'text/html')
1049 for header_value in headers_values:
1050 header_value = urllib.unquote(header_value)
1051 (key, value) = header_value.split(': ', 1)
1052 self.send_header(key, value)
1053 self.end_headers()
1054 for header_value in headers_values:
1055 self.wfile.write('%s' % header_value)
1056 return True
1057
initial.commit94958cf2008-07-26 22:42:52 +00001058 def AuthBasicHandler(self):
1059 """This handler tests 'Basic' authentication. It just sends a page with
1060 title 'user/pass' if you succeed."""
1061
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001062 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001063 return False
1064
1065 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001066 expected_password = 'secret'
1067 realm = 'testrealm'
1068 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001069
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001070 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1071 query_params = cgi.parse_qs(query, True)
1072 if 'set-cookie-if-challenged' in query_params:
1073 set_cookie_if_challenged = True
1074 if 'password' in query_params:
1075 expected_password = query_params['password'][0]
1076 if 'realm' in query_params:
1077 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001078
initial.commit94958cf2008-07-26 22:42:52 +00001079 auth = self.headers.getheader('authorization')
1080 try:
1081 if not auth:
1082 raise Exception('no auth')
1083 b64str = re.findall(r'Basic (\S+)', auth)[0]
1084 userpass = base64.b64decode(b64str)
1085 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001086 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001087 raise Exception('wrong password')
1088 except Exception, e:
1089 # Authentication failed.
1090 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001091 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001092 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001093 if set_cookie_if_challenged:
1094 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001095 self.end_headers()
1096 self.wfile.write('<html><head>')
1097 self.wfile.write('<title>Denied: %s</title>' % e)
1098 self.wfile.write('</head><body>')
1099 self.wfile.write('auth=%s<p>' % auth)
1100 self.wfile.write('b64str=%s<p>' % b64str)
1101 self.wfile.write('username: %s<p>' % username)
1102 self.wfile.write('userpass: %s<p>' % userpass)
1103 self.wfile.write('password: %s<p>' % password)
1104 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1105 self.wfile.write('</body></html>')
1106 return True
1107
1108 # Authentication successful. (Return a cachable response to allow for
1109 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001110 old_protocol_version = self.protocol_version
1111 self.protocol_version = "HTTP/1.1"
1112
initial.commit94958cf2008-07-26 22:42:52 +00001113 if_none_match = self.headers.getheader('if-none-match')
1114 if if_none_match == "abc":
1115 self.send_response(304)
1116 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001117 elif url_path.endswith(".gif"):
1118 # Using chrome/test/data/google/logo.gif as the test image
1119 test_image_path = ['google', 'logo.gif']
1120 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1121 if not os.path.isfile(gif_path):
1122 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001123 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001124 return True
1125
1126 f = open(gif_path, "rb")
1127 data = f.read()
1128 f.close()
1129
1130 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001131 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001132 self.send_header('Cache-control', 'max-age=60000')
1133 self.send_header('Etag', 'abc')
1134 self.end_headers()
1135 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001136 else:
1137 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001138 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001139 self.send_header('Cache-control', 'max-age=60000')
1140 self.send_header('Etag', 'abc')
1141 self.end_headers()
1142 self.wfile.write('<html><head>')
1143 self.wfile.write('<title>%s/%s</title>' % (username, password))
1144 self.wfile.write('</head><body>')
1145 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001146 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001147 self.wfile.write('</body></html>')
1148
rvargas@google.com54453b72011-05-19 01:11:11 +00001149 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001150 return True
1151
tonyg@chromium.org75054202010-03-31 22:06:10 +00001152 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001153 """Returns a nonce that's stable per request path for the server's lifetime.
1154 This is a fake implementation. A real implementation would only use a given
1155 nonce a single time (hence the name n-once). However, for the purposes of
1156 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001157
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001158 Args:
1159 force_reset: Iff set, the nonce will be changed. Useful for testing the
1160 "stale" response.
1161 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001162
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001163 if force_reset or not self.server.nonce_time:
1164 self.server.nonce_time = time.time()
1165 return hashlib.md5('privatekey%s%d' %
1166 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001167
1168 def AuthDigestHandler(self):
1169 """This handler tests 'Digest' authentication.
1170
1171 It just sends a page with title 'user/pass' if you succeed.
1172
1173 A stale response is sent iff "stale" is present in the request path.
1174 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001175
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001176 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001177 return False
1178
tonyg@chromium.org75054202010-03-31 22:06:10 +00001179 stale = 'stale' in self.path
1180 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001181 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001182 password = 'secret'
1183 realm = 'testrealm'
1184
1185 auth = self.headers.getheader('authorization')
1186 pairs = {}
1187 try:
1188 if not auth:
1189 raise Exception('no auth')
1190 if not auth.startswith('Digest'):
1191 raise Exception('not digest')
1192 # Pull out all the name="value" pairs as a dictionary.
1193 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1194
1195 # Make sure it's all valid.
1196 if pairs['nonce'] != nonce:
1197 raise Exception('wrong nonce')
1198 if pairs['opaque'] != opaque:
1199 raise Exception('wrong opaque')
1200
1201 # Check the 'response' value and make sure it matches our magic hash.
1202 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001203 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001204 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001205 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001206 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001207 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001208 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1209 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001210 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001211
1212 if pairs['response'] != response:
1213 raise Exception('wrong password')
1214 except Exception, e:
1215 # Authentication failed.
1216 self.send_response(401)
1217 hdr = ('Digest '
1218 'realm="%s", '
1219 'domain="/", '
1220 'qop="auth", '
1221 'algorithm=MD5, '
1222 'nonce="%s", '
1223 'opaque="%s"') % (realm, nonce, opaque)
1224 if stale:
1225 hdr += ', stale="TRUE"'
1226 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001227 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001228 self.end_headers()
1229 self.wfile.write('<html><head>')
1230 self.wfile.write('<title>Denied: %s</title>' % e)
1231 self.wfile.write('</head><body>')
1232 self.wfile.write('auth=%s<p>' % auth)
1233 self.wfile.write('pairs=%s<p>' % pairs)
1234 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1235 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1236 self.wfile.write('</body></html>')
1237 return True
1238
1239 # Authentication successful.
1240 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001241 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001242 self.end_headers()
1243 self.wfile.write('<html><head>')
1244 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1245 self.wfile.write('</head><body>')
1246 self.wfile.write('auth=%s<p>' % auth)
1247 self.wfile.write('pairs=%s<p>' % pairs)
1248 self.wfile.write('</body></html>')
1249
1250 return True
1251
1252 def SlowServerHandler(self):
1253 """Wait for the user suggested time before responding. The syntax is
1254 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001255
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001256 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001257 return False
1258 query_char = self.path.find('?')
1259 wait_sec = 1.0
1260 if query_char >= 0:
1261 try:
davidben05f82202015-03-31 13:48:07 -07001262 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001263 except ValueError:
1264 pass
1265 time.sleep(wait_sec)
1266 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001267 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001268 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001269 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001270 return True
1271
creis@google.com2f4f6a42011-03-25 19:44:19 +00001272 def NoContentHandler(self):
1273 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001274
creis@google.com2f4f6a42011-03-25 19:44:19 +00001275 if not self._ShouldHandleRequest("/nocontent"):
1276 return False
1277 self.send_response(204)
1278 self.end_headers()
1279 return True
1280
initial.commit94958cf2008-07-26 22:42:52 +00001281 def ServerRedirectHandler(self):
1282 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001283 '/server-redirect?http://foo.bar/asdf' to redirect to
1284 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001285
1286 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001287 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001288 return False
1289
1290 query_char = self.path.find('?')
1291 if query_char < 0 or len(self.path) <= query_char + 1:
1292 self.sendRedirectHelp(test_name)
1293 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001294 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001295
1296 self.send_response(301) # moved permanently
1297 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001298 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001299 self.end_headers()
1300 self.wfile.write('<html><head>')
1301 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1302
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001303 return True
initial.commit94958cf2008-07-26 22:42:52 +00001304
naskoe7a0d0d2014-09-29 08:53:05 -07001305 def CrossSiteRedirectHandler(self):
1306 """Sends a server redirect to the given site. The syntax is
1307 '/cross-site/hostname/...' to redirect to //hostname/...
1308 It is used to navigate between different Sites, causing
1309 cross-site/cross-process navigations in the browser."""
1310
1311 test_name = "/cross-site"
1312 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001313 return False
1314
1315 params = urllib.unquote(self.path[(len(test_name) + 1):])
1316 slash = params.find('/')
1317 if slash < 0:
1318 self.sendRedirectHelp(test_name)
1319 return True
1320
1321 host = params[:slash]
1322 path = params[(slash+1):]
1323 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1324
1325 self.send_response(301) # moved permanently
1326 self.send_header('Location', dest)
1327 self.send_header('Content-Type', 'text/html')
1328 self.end_headers()
1329 self.wfile.write('<html><head>')
1330 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1331
1332 return True
1333
initial.commit94958cf2008-07-26 22:42:52 +00001334 def ClientRedirectHandler(self):
1335 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001336 '/client-redirect?http://foo.bar/asdf' to redirect to
1337 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001338
1339 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001340 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001341 return False
1342
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001343 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001344 if query_char < 0 or len(self.path) <= query_char + 1:
1345 self.sendRedirectHelp(test_name)
1346 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001347 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001348
1349 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001350 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001351 self.end_headers()
1352 self.wfile.write('<html><head>')
1353 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1354 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1355
1356 return True
1357
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001358 def GetSSLSessionCacheHandler(self):
1359 """Send a reply containing a log of the session cache operations."""
1360
1361 if not self._ShouldHandleRequest('/ssl-session-cache'):
1362 return False
1363
1364 self.send_response(200)
1365 self.send_header('Content-Type', 'text/plain')
1366 self.end_headers()
1367 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001368 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001369 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001370 self.wfile.write('Pass --https-record-resume in order to use' +
1371 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001372 return True
1373
1374 for (action, sessionID) in log:
1375 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001376 return True
1377
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001378 def SSLManySmallRecords(self):
1379 """Sends a reply consisting of a variety of small writes. These will be
1380 translated into a series of small SSL records when used over an HTTPS
1381 server."""
1382
1383 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1384 return False
1385
1386 self.send_response(200)
1387 self.send_header('Content-Type', 'text/plain')
1388 self.end_headers()
1389
1390 # Write ~26K of data, in 1350 byte chunks
1391 for i in xrange(20):
1392 self.wfile.write('*' * 1350)
1393 self.wfile.flush()
1394 return True
1395
agl@chromium.org04700be2013-03-02 18:40:41 +00001396 def GetChannelID(self):
1397 """Send a reply containing the hashed ChannelID that the client provided."""
1398
1399 if not self._ShouldHandleRequest('/channel-id'):
1400 return False
1401
1402 self.send_response(200)
1403 self.send_header('Content-Type', 'text/plain')
1404 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001405 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001406 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1407 return True
1408
pneubeckfd4f0442015-08-07 04:55:10 -07001409 def GetClientCert(self):
1410 """Send a reply whether a client certificate was provided."""
1411
1412 if not self._ShouldHandleRequest('/client-cert'):
1413 return False
1414
1415 self.send_response(200)
1416 self.send_header('Content-Type', 'text/plain')
1417 self.end_headers()
1418
1419 cert_chain = self.server.tlsConnection.session.clientCertChain
1420 if cert_chain != None:
1421 self.wfile.write('got client cert with fingerprint: ' +
1422 cert_chain.getFingerprint())
1423 else:
1424 self.wfile.write('got no client cert')
1425 return True
1426
davidben599e7e72014-09-03 16:19:09 -07001427 def ClientCipherListHandler(self):
1428 """Send a reply containing the cipher suite list that the client
1429 provided. Each cipher suite value is serialized in decimal, followed by a
1430 newline."""
1431
1432 if not self._ShouldHandleRequest('/client-cipher-list'):
1433 return False
1434
1435 self.send_response(200)
1436 self.send_header('Content-Type', 'text/plain')
1437 self.end_headers()
1438
davidben11682512014-10-06 21:09:11 -07001439 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1440 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001441 return True
1442
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001443 def CloseSocketHandler(self):
1444 """Closes the socket without sending anything."""
1445
1446 if not self._ShouldHandleRequest('/close-socket'):
1447 return False
1448
1449 self.wfile.close()
1450 return True
1451
initial.commit94958cf2008-07-26 22:42:52 +00001452 def DefaultResponseHandler(self):
1453 """This is the catch-all response handler for requests that aren't handled
1454 by one of the special handlers above.
1455 Note that we specify the content-length as without it the https connection
1456 is not closed properly (and the browser keeps expecting data)."""
1457
1458 contents = "Default response given for path: " + self.path
1459 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001460 self.send_header('Content-Type', 'text/html')
1461 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001462 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001463 if (self.command != 'HEAD'):
1464 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001465 return True
1466
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001467 def RedirectConnectHandler(self):
1468 """Sends a redirect to the CONNECT request for www.redirect.com. This
1469 response is not specified by the RFC, so the browser should not follow
1470 the redirect."""
1471
1472 if (self.path.find("www.redirect.com") < 0):
1473 return False
1474
1475 dest = "http://www.destination.com/foo.js"
1476
1477 self.send_response(302) # moved temporarily
1478 self.send_header('Location', dest)
1479 self.send_header('Connection', 'close')
1480 self.end_headers()
1481 return True
1482
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001483 def ServerAuthConnectHandler(self):
1484 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1485 response doesn't make sense because the proxy server cannot request
1486 server authentication."""
1487
1488 if (self.path.find("www.server-auth.com") < 0):
1489 return False
1490
1491 challenge = 'Basic realm="WallyWorld"'
1492
1493 self.send_response(401) # unauthorized
1494 self.send_header('WWW-Authenticate', challenge)
1495 self.send_header('Connection', 'close')
1496 self.end_headers()
1497 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001498
1499 def DefaultConnectResponseHandler(self):
1500 """This is the catch-all response handler for CONNECT requests that aren't
1501 handled by one of the special handlers above. Real Web servers respond
1502 with 400 to CONNECT requests."""
1503
1504 contents = "Your client has issued a malformed or illegal request."
1505 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001506 self.send_header('Content-Type', 'text/html')
1507 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001508 self.end_headers()
1509 self.wfile.write(contents)
1510 return True
1511
initial.commit94958cf2008-07-26 22:42:52 +00001512 # called by the redirect handling function when there is no parameter
1513 def sendRedirectHelp(self, redirect_name):
1514 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001516 self.end_headers()
1517 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1518 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1519 self.wfile.write('</body></html>')
1520
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001521 # called by chunked handling function
1522 def sendChunkHelp(self, chunk):
1523 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1524 self.wfile.write('%X\r\n' % len(chunk))
1525 self.wfile.write(chunk)
1526 self.wfile.write('\r\n')
1527
akalin@chromium.org154bb132010-11-12 02:20:27 +00001528
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001529class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001530 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001531 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001532 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001533 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001534 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001535 testserver_base.BasePageHandler.__init__(self, request, client_address,
1536 socket_server, [], handlers, [],
1537 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001538
1539 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001540 if self._ShouldHandleRequest("/ocsp"):
1541 response = self.ocsp_response
1542 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1543 response = self.ocsp_response_intermediate
1544 else:
mattm10ede842016-11-29 11:57:16 -08001545 return False
1546 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001547 self.send_response(200)
1548 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001549 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001550 self.end_headers()
1551
Matt Mueller55aef642018-05-02 18:53:57 +00001552 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001553
mattm10ede842016-11-29 11:57:16 -08001554 def CaIssuersResponse(self):
1555 if not self._ShouldHandleRequest("/ca_issuers"):
1556 return False
1557 print 'handling ca_issuers request'
1558 self.send_response(200)
1559 self.send_header('Content-Type', 'application/pkix-cert')
1560 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1561 self.end_headers()
1562
1563 self.wfile.write(self.ca_issuers_response)
1564
mattm@chromium.org830a3712012-11-07 23:00:07 +00001565
Adam Rice9476b8c2018-08-02 15:28:43 +00001566class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1567 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1568 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001569 """
1570
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001571 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001572
bashi@chromium.org33233532012-09-08 17:37:24 +00001573 def _start_read_write(self, sock):
1574 sock.setblocking(0)
1575 self.request.setblocking(0)
1576 rlist = [self.request, sock]
1577 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001578 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001579 if errors:
1580 self.send_response(500)
1581 self.end_headers()
1582 return
1583 for s in ready_sockets:
1584 received = s.recv(1024)
1585 if len(received) == 0:
1586 return
1587 if s == self.request:
1588 other = sock
1589 else:
1590 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001591 # This will lose data if the kernel write buffer fills up.
1592 # TODO(ricea): Correctly use the return value to track how much was
1593 # written and buffer the rest. Use select to determine when the socket
1594 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001595 other.send(received)
1596
1597 def _do_common_method(self):
1598 url = urlparse.urlparse(self.path)
1599 port = url.port
1600 if not port:
1601 if url.scheme == 'http':
1602 port = 80
1603 elif url.scheme == 'https':
1604 port = 443
1605 if not url.hostname or not port:
1606 self.send_response(400)
1607 self.end_headers()
1608 return
1609
1610 if len(url.path) == 0:
1611 path = '/'
1612 else:
1613 path = url.path
1614 if len(url.query) > 0:
1615 path = '%s?%s' % (url.path, url.query)
1616
1617 sock = None
1618 try:
1619 sock = socket.create_connection((url.hostname, port))
1620 sock.send('%s %s %s\r\n' % (
1621 self.command, path, self.protocol_version))
1622 for header in self.headers.headers:
1623 header = header.strip()
1624 if (header.lower().startswith('connection') or
1625 header.lower().startswith('proxy')):
1626 continue
1627 sock.send('%s\r\n' % header)
1628 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001629 # This is wrong: it will pass through connection-level headers and
1630 # misbehave on connection reuse. The only reason it works at all is that
1631 # our test servers have never supported connection reuse.
1632 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001633 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001634 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001635 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001636 self.send_response(500)
1637 self.end_headers()
1638 finally:
1639 if sock is not None:
1640 sock.close()
1641
1642 def do_CONNECT(self):
1643 try:
1644 pos = self.path.rfind(':')
1645 host = self.path[:pos]
1646 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001647 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001648 self.send_response(400)
1649 self.end_headers()
1650
Adam Rice9476b8c2018-08-02 15:28:43 +00001651 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001652 host = "127.0.0.1"
1653
Adam Rice54443aa2018-06-06 00:11:54 +00001654 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001655 try:
1656 sock = socket.create_connection((host, port))
1657 self.send_response(200, 'Connection established')
1658 self.end_headers()
1659 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001660 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001661 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001662 self.send_response(500)
1663 self.end_headers()
1664 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001665 if sock is not None:
1666 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001667
1668 def do_GET(self):
1669 self._do_common_method()
1670
1671 def do_HEAD(self):
1672 self._do_common_method()
1673
Adam Rice9476b8c2018-08-02 15:28:43 +00001674class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1675 """A request handler that behaves as a proxy server which requires
1676 basic authentication.
1677 """
1678
1679 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1680
1681 def parse_request(self):
1682 """Overrides parse_request to check credential."""
1683
1684 if not ProxyRequestHandler.parse_request(self):
1685 return False
1686
1687 auth = self.headers.getheader('Proxy-Authorization')
1688 if auth != self._AUTH_CREDENTIAL:
1689 self.send_response(407)
1690 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1691 self.end_headers()
1692 return False
1693
1694 return True
1695
bashi@chromium.org33233532012-09-08 17:37:24 +00001696
mattm@chromium.org830a3712012-11-07 23:00:07 +00001697class ServerRunner(testserver_base.TestServerRunner):
1698 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001699
mattm@chromium.org830a3712012-11-07 23:00:07 +00001700 def __init__(self):
1701 super(ServerRunner, self).__init__()
1702 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001703
mattm@chromium.org830a3712012-11-07 23:00:07 +00001704 def __make_data_dir(self):
1705 if self.options.data_dir:
1706 if not os.path.isdir(self.options.data_dir):
1707 raise testserver_base.OptionError('specified data dir not found: ' +
1708 self.options.data_dir + ' exiting...')
1709 my_data_dir = self.options.data_dir
1710 else:
1711 # Create the default path to our data dir, relative to the exe dir.
Asanka Herath0ec37152019-08-02 15:23:57 +00001712 my_data_dir = os.path.join(BASE_DIR, "..", "..", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001713
mattm@chromium.org830a3712012-11-07 23:00:07 +00001714 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001715
Matt Mueller55aef642018-05-02 18:53:57 +00001716 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1717 if states_option is None:
1718 return None, None, None
1719
1720 ocsp_states = list()
1721 for ocsp_state_arg in states_option.split(':'):
1722 if ocsp_state_arg == 'ok':
1723 ocsp_state = minica.OCSP_STATE_GOOD
1724 elif ocsp_state_arg == 'revoked':
1725 ocsp_state = minica.OCSP_STATE_REVOKED
1726 elif ocsp_state_arg == 'invalid':
1727 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1728 elif ocsp_state_arg == 'unauthorized':
1729 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1730 elif ocsp_state_arg == 'unknown':
1731 ocsp_state = minica.OCSP_STATE_UNKNOWN
1732 elif ocsp_state_arg == 'later':
1733 ocsp_state = minica.OCSP_STATE_TRY_LATER
1734 elif ocsp_state_arg == 'invalid_data':
1735 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1736 elif ocsp_state_arg == "mismatched_serial":
1737 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1738 else:
1739 raise testserver_base.OptionError('unknown OCSP status: ' +
1740 ocsp_state_arg)
1741 ocsp_states.append(ocsp_state)
1742
1743 if len(ocsp_states) > 1:
1744 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1745 raise testserver_base.OptionError('Multiple OCSP responses '
1746 'incompatible with states ' + str(ocsp_states))
1747
1748 ocsp_dates = list()
1749 for ocsp_date_arg in date_option.split(':'):
1750 if ocsp_date_arg == 'valid':
1751 ocsp_date = minica.OCSP_DATE_VALID
1752 elif ocsp_date_arg == 'old':
1753 ocsp_date = minica.OCSP_DATE_OLD
1754 elif ocsp_date_arg == 'early':
1755 ocsp_date = minica.OCSP_DATE_EARLY
1756 elif ocsp_date_arg == 'long':
1757 ocsp_date = minica.OCSP_DATE_LONG
1758 elif ocsp_date_arg == 'longer':
1759 ocsp_date = minica.OCSP_DATE_LONGER
1760 else:
1761 raise testserver_base.OptionError('unknown OCSP date: ' +
1762 ocsp_date_arg)
1763 ocsp_dates.append(ocsp_date)
1764
1765 if len(ocsp_states) != len(ocsp_dates):
1766 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1767 'count')
1768
1769 ocsp_produced = None
1770 if produced_option == 'valid':
1771 ocsp_produced = minica.OCSP_PRODUCED_VALID
1772 elif produced_option == 'before':
1773 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1774 elif produced_option == 'after':
1775 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1776 else:
1777 raise testserver_base.OptionError('unknown OCSP produced: ' +
1778 produced_option)
1779
1780 return ocsp_states, ocsp_dates, ocsp_produced
1781
mattm@chromium.org830a3712012-11-07 23:00:07 +00001782 def create_server(self, server_data):
1783 port = self.options.port
1784 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001785
Adam Rice54443aa2018-06-06 00:11:54 +00001786 logging.basicConfig()
1787
estark21667d62015-04-08 21:00:16 -07001788 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1789 # will result in a call to |getaddrinfo|, which fails with "nodename
1790 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001791 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001792 if self.options.server_type == SERVER_WEBSOCKET and \
1793 host == "localhost" and \
1794 port == 0:
1795 host = "127.0.0.1"
1796
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001797 # Construct the subjectAltNames for any ad-hoc generated certificates.
1798 # As host can be either a DNS name or IP address, attempt to determine
1799 # which it is, so it can be placed in the appropriate SAN.
1800 dns_sans = None
1801 ip_sans = None
1802 ip = None
1803 try:
1804 ip = socket.inet_aton(host)
1805 ip_sans = [ip]
1806 except socket.error:
1807 pass
1808 if ip is None:
1809 dns_sans = [host]
1810
mattm@chromium.org830a3712012-11-07 23:00:07 +00001811 if self.options.server_type == SERVER_HTTP:
1812 if self.options.https:
1813 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001814 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001815 if self.options.cert_and_key_file:
1816 if not os.path.isfile(self.options.cert_and_key_file):
1817 raise testserver_base.OptionError(
1818 'specified server cert file not found: ' +
1819 self.options.cert_and_key_file + ' exiting...')
1820 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001821 elif self.options.aia_intermediate:
1822 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1823 print ('AIA server started on %s:%d...' %
1824 (host, self.__ocsp_server.server_port))
1825
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001826 ocsp_server_port = self.__ocsp_server.server_port
1827 if self.options.ocsp_proxy_port_number != 0:
1828 ocsp_server_port = self.options.ocsp_proxy_port_number
1829 server_data['ocsp_port'] = self.__ocsp_server.server_port
1830
mattm10ede842016-11-29 11:57:16 -08001831 (pem_cert_and_key, intermediate_cert_der) = \
1832 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001833 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001834 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001835 ca_issuers_url =
1836 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08001837 serial = self.options.cert_serial)
1838
1839 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00001840 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08001841 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001842 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001843 # generate a new certificate and run an OCSP server for it.
1844 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001845 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001846 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001847
Matt Mueller55aef642018-05-02 18:53:57 +00001848 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
1849 self.options.ocsp,
1850 self.options.ocsp_date,
1851 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001852
Matt Mueller55aef642018-05-02 18:53:57 +00001853 (ocsp_intermediate_states, ocsp_intermediate_dates,
1854 ocsp_intermediate_produced) = self.__parse_ocsp_options(
1855 self.options.ocsp_intermediate,
1856 self.options.ocsp_intermediate_date,
1857 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001858
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001859 ocsp_server_port = self.__ocsp_server.server_port
1860 if self.options.ocsp_proxy_port_number != 0:
1861 ocsp_server_port = self.options.ocsp_proxy_port_number
1862 server_data['ocsp_port'] = self.__ocsp_server.server_port
1863
Matt Mueller55aef642018-05-02 18:53:57 +00001864 pem_cert_and_key, (ocsp_der,
1865 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00001866 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001867 ip_sans = ip_sans,
1868 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001869 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07001870 ocsp_states = ocsp_states,
1871 ocsp_dates = ocsp_dates,
1872 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00001873 ocsp_intermediate_url = (
1874 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
1875 if ocsp_intermediate_states else None),
1876 ocsp_intermediate_states = ocsp_intermediate_states,
1877 ocsp_intermediate_dates = ocsp_intermediate_dates,
1878 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00001879 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001880
davidben3e2564a2014-11-07 18:51:00 -08001881 if self.options.ocsp_server_unavailable:
1882 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00001883 self.__ocsp_server.ocsp_response_intermediate = \
1884 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08001885 else:
1886 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00001887 self.__ocsp_server.ocsp_response_intermediate = \
1888 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08001889 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001890
1891 for ca_cert in self.options.ssl_client_ca:
1892 if not os.path.isfile(ca_cert):
1893 raise testserver_base.OptionError(
1894 'specified trusted client CA file not found: ' + ca_cert +
1895 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001896
1897 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08001898 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00001899 # TODO(mattm): Staple the intermediate response too (if applicable,
1900 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08001901 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001902
mattm@chromium.org830a3712012-11-07 23:00:07 +00001903 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1904 self.options.ssl_client_auth,
1905 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001906 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001907 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001908 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07001909 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07001910 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001911 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001912 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001913 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001914 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001915 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001916 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07001917 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07001918 self.options.alert_after_handshake,
1919 self.options.disable_channel_id,
David Benjaminf839f1c2018-10-16 06:01:29 +00001920 self.options.disable_extended_master_secret,
1921 self.options.simulate_tls13_downgrade,
1922 self.options.simulate_tls12_downgrade,
1923 self.options.tls_max_version)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001924 print 'HTTPS server started on https://%s:%d...' % \
1925 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001926 else:
1927 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001928 print 'HTTP server started on http://%s:%d...' % \
1929 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001930
1931 server.data_dir = self.__make_data_dir()
1932 server.file_root_url = self.options.file_root_url
1933 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001934 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001935 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1936 # is required to work correctly. It should be fixed from pywebsocket side.
1937 os.chdir(self.__make_data_dir())
1938 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00001939 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00001941 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00001943 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
1944 if not os.path.isfile(key_path):
1945 raise testserver_base.OptionError(
1946 'specified server cert file not found: ' +
1947 self.options.cert_and_key_file + ' exiting...')
1948 websocket_options.private_key = key_path
1949 websocket_options.certificate = key_path
1950
mattm@chromium.org830a3712012-11-07 23:00:07 +00001951 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00001952 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00001953 websocket_options.tls_client_auth = True
1954 if len(self.options.ssl_client_ca) != 1:
1955 raise testserver_base.OptionError(
1956 'one trusted client CA file should be specified')
1957 if not os.path.isfile(self.options.ssl_client_ca[0]):
1958 raise testserver_base.OptionError(
1959 'specified trusted client CA file not found: ' +
1960 self.options.ssl_client_ca[0] + ' exiting...')
1961 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07001962 print 'Trying to start websocket server on %s://%s:%d...' % \
1963 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001964 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001965 print 'WebSocket server started on %s://%s:%d...' % \
1966 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001967 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00001968 websocket_options.use_basic_auth = self.options.ws_basic_auth
Adam Rice9476b8c2018-08-02 15:28:43 +00001969 elif self.options.server_type == SERVER_PROXY:
1970 ProxyRequestHandler.redirect_connect_to_localhost = \
1971 self.options.redirect_connect_to_localhost
1972 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
1973 print 'Proxy server started on port %d...' % server.server_port
1974 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00001976 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001977 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00001978 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001979 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001980 server_data['port'] = server.server_port
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001981 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001982 raise testserver_base.OptionError('unknown server type' +
1983 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00001984
mattm@chromium.org830a3712012-11-07 23:00:07 +00001985 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001986
mattm@chromium.org830a3712012-11-07 23:00:07 +00001987 def run_server(self):
1988 if self.__ocsp_server:
1989 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001990
mattm@chromium.org830a3712012-11-07 23:00:07 +00001991 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001992
mattm@chromium.org830a3712012-11-07 23:00:07 +00001993 if self.__ocsp_server:
1994 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001995
mattm@chromium.org830a3712012-11-07 23:00:07 +00001996 def add_options(self):
1997 testserver_base.TestServerRunner.add_options(self)
Adam Rice9476b8c2018-08-02 15:28:43 +00001998 self.option_parser.add_option('--proxy', action='store_const',
1999 const=SERVER_PROXY,
2000 default=SERVER_HTTP, dest='server_type',
2001 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2003 const=SERVER_BASIC_AUTH_PROXY,
2004 default=SERVER_HTTP, dest='server_type',
2005 help='start up a proxy server which requires '
2006 'basic authentication.')
2007 self.option_parser.add_option('--websocket', action='store_const',
2008 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2009 dest='server_type',
2010 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002011 self.option_parser.add_option('--https', action='store_true',
2012 dest='https', help='Specify that https '
2013 'should be used.')
2014 self.option_parser.add_option('--cert-and-key-file',
2015 dest='cert_and_key_file', help='specify the '
2016 'path to the file containing the certificate '
2017 'and private key for the server in PEM '
2018 'format')
mattm10ede842016-11-29 11:57:16 -08002019 self.option_parser.add_option('--aia-intermediate', action='store_true',
2020 dest='aia_intermediate',
2021 help='generate a certificate chain that '
2022 'requires AIA cert fetching, and run a '
2023 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002024 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2025 help='The type of OCSP response generated '
2026 'for the automatically generated '
2027 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002028 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2029 default='valid', help='The validity of the '
2030 'range between thisUpdate and nextUpdate')
2031 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2032 default='valid', help='producedAt relative '
2033 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002034 self.option_parser.add_option('--ocsp-intermediate',
2035 dest='ocsp_intermediate', default=None,
2036 help='If specified, the automatically '
2037 'generated chain will include an '
2038 'intermediate certificate with this type '
2039 'of OCSP response (see docs for --ocsp)')
2040 self.option_parser.add_option('--ocsp-intermediate-date',
2041 dest='ocsp_intermediate_date',
2042 default='valid', help='The validity of the '
2043 'range between thisUpdate and nextUpdate')
2044 self.option_parser.add_option('--ocsp-intermediate-produced',
2045 dest='ocsp_intermediate_produced',
2046 default='valid', help='producedAt relative '
2047 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002048 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2049 default=0, type=int,
2050 help='If non-zero then the generated '
2051 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002052 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2053 default="127.0.0.1",
2054 help='The generated certificate will have '
2055 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002056 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2057 default='0', type='int',
2058 help='If nonzero, certain TLS connections '
2059 'will be aborted in order to test version '
2060 'fallback. 1 means all TLS versions will be '
2061 'aborted. 2 means TLS 1.1 or higher will be '
2062 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002063 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002064 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002065 self.option_parser.add_option('--tls-intolerance-type',
2066 dest='tls_intolerance_type',
2067 default="alert",
2068 help='Controls how the server reacts to a '
2069 'TLS version it is intolerant to. Valid '
2070 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002071 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2072 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002073 default='',
2074 help='Base64 encoded SCT list. If set, '
2075 'server will respond with a '
2076 'signed_certificate_timestamp TLS extension '
2077 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002078 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2079 default=False, const=True,
2080 action='store_const',
2081 help='If given, TLS_FALLBACK_SCSV support '
2082 'will be enabled. This causes the server to '
2083 'reject fallback connections from compatible '
2084 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002085 self.option_parser.add_option('--staple-ocsp-response',
2086 dest='staple_ocsp_response',
2087 default=False, action='store_true',
2088 help='If set, server will staple the OCSP '
2089 'response whenever OCSP is on and the client '
2090 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002091 self.option_parser.add_option('--https-record-resume',
2092 dest='record_resume', const=True,
2093 default=False, action='store_const',
2094 help='Record resumption cache events rather '
2095 'than resuming as normal. Allows the use of '
2096 'the /ssl-session-cache request')
2097 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2098 help='Require SSL client auth on every '
2099 'connection.')
2100 self.option_parser.add_option('--ssl-client-ca', action='append',
2101 default=[], help='Specify that the client '
2102 'certificate request should include the CA '
2103 'named in the subject of the DER-encoded '
2104 'certificate contained in the specified '
2105 'file. This option may appear multiple '
2106 'times, indicating multiple CA names should '
2107 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002108 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2109 default=[], help='Specify that the client '
2110 'certificate request should include the '
2111 'specified certificate_type value. This '
2112 'option may appear multiple times, '
2113 'indicating multiple values should be send '
2114 'in the request. Valid values are '
2115 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2116 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002117 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2118 help='Specify the bulk encryption '
2119 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002120 'SSL server. Valid values are "aes128gcm", '
2121 '"aes256", "aes128", "3des", "rc4". If '
2122 'omitted, all algorithms will be used. This '
2123 'option may appear multiple times, '
2124 'indicating multiple algorithms should be '
2125 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002126 self.option_parser.add_option('--ssl-key-exchange', action='append',
2127 help='Specify the key exchange algorithm(s)'
2128 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002129 'Valid values are "rsa", "dhe_rsa", '
2130 '"ecdhe_rsa". If omitted, all algorithms '
2131 'will be used. This option may appear '
2132 'multiple times, indicating multiple '
2133 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002134 self.option_parser.add_option('--alpn-protocols', action='append',
2135 help='Specify the list of ALPN protocols. '
2136 'The server will not send an ALPN response '
2137 'if this list does not overlap with the '
2138 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002139 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002140 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002141 'an NPN response. The server will not'
2142 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002143 self.option_parser.add_option('--file-root-url', default='/files/',
2144 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002145 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2146 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2147 dest='ws_basic_auth',
2148 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002149 self.option_parser.add_option('--ocsp-server-unavailable',
2150 dest='ocsp_server_unavailable',
2151 default=False, action='store_true',
2152 help='If set, the OCSP server will return '
2153 'a tryLater status rather than the actual '
2154 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002155 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2156 type='int', dest='ocsp_proxy_port_number',
2157 help='Port allocated for OCSP proxy '
2158 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002159 self.option_parser.add_option('--alert-after-handshake',
2160 dest='alert_after_handshake',
2161 default=False, action='store_true',
2162 help='If set, the server will send a fatal '
2163 'alert immediately after the handshake.')
nharper1e8bf4b2015-09-18 12:23:02 -07002164 self.option_parser.add_option('--disable-channel-id', action='store_true')
2165 self.option_parser.add_option('--disable-extended-master-secret',
2166 action='store_true')
David Benjaminf839f1c2018-10-16 06:01:29 +00002167 self.option_parser.add_option('--simulate-tls13-downgrade',
2168 action='store_true')
2169 self.option_parser.add_option('--simulate-tls12-downgrade',
2170 action='store_true')
2171 self.option_parser.add_option('--tls-max-version', default='0', type='int',
2172 help='If non-zero, the maximum TLS version '
2173 'to support. 1 means TLS 1.0, 2 means '
2174 'TLS 1.1, and 3 means TLS 1.2.')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002175 self.option_parser.add_option('--redirect-connect-to-localhost',
2176 dest='redirect_connect_to_localhost',
2177 default=False, action='store_true',
2178 help='If set, the Proxy server will connect '
2179 'to localhost instead of the requested URL '
2180 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002181
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002182
initial.commit94958cf2008-07-26 22:42:52 +00002183if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002184 sys.exit(ServerRunner().main())