blob: 0d1e43a33e6eb265bfc25b0a631cafdf71796d87 [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
Adam Rice9476b8c2018-08-02 15:28:43 +00006"""This is a simple HTTP/FTP/TCP/UDP/PROXY/BASIC_AUTH_PROXY/WEBSOCKET server
7used for testing 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 pyftpdlib.ftpserver
54
55import tlslite
56import tlslite.api
57
davidben@chromium.org7d53b542014-04-10 17:56:44 +000058import testserver_base
59
maruel@chromium.org756cf982009-03-05 12:46:38 +000060SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000061SERVER_FTP = 1
Matt Menke858064b2020-11-03 05:54:12 +000062SERVER_BASIC_AUTH_PROXY = 2
63SERVER_WEBSOCKET = 3
64SERVER_PROXY = 4
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000065
66# Default request queue size for WebSocketServer.
67_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000068
dadrian4ccf51c2016-07-20 15:36:58 -070069OCSP_STATES_NO_SINGLE_RESPONSE = {
70 minica.OCSP_STATE_INVALID_RESPONSE,
71 minica.OCSP_STATE_UNAUTHORIZED,
72 minica.OCSP_STATE_TRY_LATER,
73 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
74}
75
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000076class WebSocketOptions:
77 """Holds options for WebSocketServer."""
78
79 def __init__(self, host, port, data_dir):
80 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
81 self.server_host = host
82 self.port = port
83 self.websock_handlers = data_dir
84 self.scan_dir = None
85 self.allow_handlers_outside_root_dir = False
86 self.websock_handlers_map_file = None
87 self.cgi_directories = []
88 self.is_executable_method = None
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000089
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000090 self.use_tls = False
91 self.private_key = None
92 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000093 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000094 self.tls_client_ca = None
95 self.use_basic_auth = False
Keita Suzuki83e26f92020-03-06 09:42:48 +000096 self.basic_auth_credential = 'Basic ' + base64.b64encode(
97 'test:test').decode()
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000098
mattm@chromium.org830a3712012-11-07 23:00:07 +000099
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000100class RecordingSSLSessionCache(object):
101 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
102 lookups and inserts in order to test session cache behaviours."""
103
104 def __init__(self):
105 self.log = []
106
107 def __getitem__(self, sessionID):
108 self.log.append(('lookup', sessionID))
109 raise KeyError()
110
111 def __setitem__(self, sessionID, session):
112 self.log.append(('insert', sessionID))
113
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000114
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000115class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
116 testserver_base.BrokenPipeHandlerMixIn,
117 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000118 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000119 verification."""
120
121 pass
122
Adam Rice34b2e312018-04-06 16:48:30 +0000123class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
124 HTTPServer):
125 """This variant of HTTPServer creates a new thread for every connection. It
126 should only be used with handlers that are known to be threadsafe."""
127
128 pass
129
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000130class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
131 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000132 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000133 """This is a specialization of HTTPServer that serves an
134 OCSP response"""
135
136 def serve_forever_on_thread(self):
137 self.thread = threading.Thread(target = self.serve_forever,
138 name = "OCSPServerThread")
139 self.thread.start()
140
141 def stop_serving(self):
142 self.shutdown()
143 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000144
mattm@chromium.org830a3712012-11-07 23:00:07 +0000145
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000146class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000147 testserver_base.ClientRestrictingServerMixIn,
148 testserver_base.BrokenPipeHandlerMixIn,
149 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000150 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000151 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000152
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000153 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000154 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700155 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
156 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000157 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700158 fallback_scsv_enabled, ocsp_response,
David Benjaminf839f1c2018-10-16 06:01:29 +0000159 alert_after_handshake, disable_channel_id, disable_ems,
160 simulate_tls13_downgrade, simulate_tls12_downgrade,
161 tls_max_version):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000162 self.cert_chain = tlslite.api.X509CertChain()
163 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000164 # Force using only python implementation - otherwise behavior is different
165 # depending on whether m2crypto Python module is present (error is thrown
166 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
167 # the hood.
168 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
169 private=True,
170 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000171 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000172 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000173 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700174 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000175 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000176 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000177 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000178
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000179 if ssl_client_auth:
180 for ca_file in ssl_client_cas:
181 s = open(ca_file).read()
182 x509 = tlslite.api.X509()
183 x509.parse(s)
184 self.ssl_client_cas.append(x509.subject)
185
186 for cert_type in ssl_client_cert_types:
187 self.ssl_client_cert_types.append({
188 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000189 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
190 }[cert_type])
191
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000192 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800193 # Enable SSLv3 for testing purposes.
194 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 if ssl_bulk_ciphers is not None:
196 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000197 if ssl_key_exchanges is not None:
198 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000199 if tls_intolerant != 0:
200 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
201 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700202 if alert_after_handshake:
203 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700204 if disable_channel_id:
205 self.ssl_handshake_settings.enableChannelID = False
206 if disable_ems:
207 self.ssl_handshake_settings.enableExtendedMasterSecret = False
David Benjaminf839f1c2018-10-16 06:01:29 +0000208 if simulate_tls13_downgrade:
209 self.ssl_handshake_settings.simulateTLS13Downgrade = True
210 if simulate_tls12_downgrade:
211 self.ssl_handshake_settings.simulateTLS12Downgrade = True
212 if tls_max_version != 0:
213 self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
bnc5fb33bd2016-08-05 12:09:21 -0700214 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000215
rsleevi8146efa2015-03-16 12:31:24 -0700216 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000217 # If record_resume_info is true then we'll replace the session cache with
218 # an object that records the lookups and inserts that it sees.
219 self.session_cache = RecordingSSLSessionCache()
220 else:
221 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000222 testserver_base.StoppableHTTPServer.__init__(self,
223 server_address,
224 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000225
226 def handshake(self, tlsConnection):
227 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000228
initial.commit94958cf2008-07-26 22:42:52 +0000229 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000230 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000231 tlsConnection.handshakeServer(certChain=self.cert_chain,
232 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000233 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000234 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000235 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000236 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000237 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700238 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000239 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000240 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000241 fallbackSCSV=self.fallback_scsv_enabled,
242 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000243 tlsConnection.ignoreAbruptClose = True
244 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000245 except tlslite.api.TLSAbruptCloseError:
246 # Ignore abrupt close.
247 return True
initial.commit94958cf2008-07-26 22:42:52 +0000248 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000249 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000250 return False
251
akalin@chromium.org154bb132010-11-12 02:20:27 +0000252
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000253class FTPServer(testserver_base.ClientRestrictingServerMixIn,
254 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000255 """This is a specialization of FTPServer that adds client verification."""
256
257 pass
258
259
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000260class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000261 # Class variables to allow for persistence state between page handler
262 # invocations
263 rst_limits = {}
264 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000265
266 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000267 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000268 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000269 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000270 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000271 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000272 self.NoCacheMaxAgeTimeHandler,
273 self.NoCacheTimeHandler,
274 self.CacheTimeHandler,
275 self.CacheExpiresHandler,
276 self.CacheProxyRevalidateHandler,
277 self.CachePrivateHandler,
278 self.CachePublicHandler,
279 self.CacheSMaxAgeHandler,
280 self.CacheMustRevalidateHandler,
281 self.CacheMustRevalidateMaxAgeHandler,
282 self.CacheNoStoreHandler,
283 self.CacheNoStoreMaxAgeHandler,
284 self.CacheNoTransformHandler,
285 self.DownloadHandler,
286 self.DownloadFinishHandler,
287 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000288 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000289 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000290 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000291 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000292 self.SetCookieHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000293 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000294 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000295 self.AuthBasicHandler,
296 self.AuthDigestHandler,
297 self.SlowServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000298 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000299 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700300 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000301 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000302 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000303 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000304 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700305 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700306 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000307 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000308 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000309 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000310 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000311 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000312 self.PostOnlyFileHandler,
313 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000314 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000315 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000316 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000317 head_handlers = [
318 self.FileHandler,
319 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000320
maruel@google.come250a9b2009-03-10 17:39:46 +0000321 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000322 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000323 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000324 'gif': 'image/gif',
325 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000326 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700327 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000328 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000329 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000330 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000331 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000332 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000333 }
initial.commit94958cf2008-07-26 22:42:52 +0000334 self._default_mime_type = 'text/html'
335
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000336 testserver_base.BasePageHandler.__init__(self, request, client_address,
337 socket_server, connect_handlers,
338 get_handlers, head_handlers,
339 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000340
initial.commit94958cf2008-07-26 22:42:52 +0000341 def GetMIMETypeFromName(self, file_name):
342 """Returns the mime type for the specified file_name. So far it only looks
343 at the file extension."""
344
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000345 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000346 if len(extension) == 0:
347 # no extension.
348 return self._default_mime_type
349
ericroman@google.comc17ca532009-05-07 03:51:05 +0000350 # extension starts with a dot, so we need to remove it
351 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000352
initial.commit94958cf2008-07-26 22:42:52 +0000353 def NoCacheMaxAgeTimeHandler(self):
354 """This request handler yields a page with the title set to the current
355 system time, and no caching requested."""
356
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000357 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000358 return False
359
360 self.send_response(200)
361 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000362 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000363 self.end_headers()
364
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 self.wfile.write('<html><head><title>%s</title></head></html>' %
366 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000367
368 return True
369
370 def NoCacheTimeHandler(self):
371 """This request handler yields a page with the title set to the current
372 system time, and no caching requested."""
373
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000374 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000375 return False
376
377 self.send_response(200)
378 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000379 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000380 self.end_headers()
381
maruel@google.come250a9b2009-03-10 17:39:46 +0000382 self.wfile.write('<html><head><title>%s</title></head></html>' %
383 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000384
385 return True
386
387 def CacheTimeHandler(self):
388 """This request handler yields a page with the title set to the current
389 system time, and allows caching for one minute."""
390
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000391 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000392 return False
393
394 self.send_response(200)
395 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000396 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000397 self.end_headers()
398
maruel@google.come250a9b2009-03-10 17:39:46 +0000399 self.wfile.write('<html><head><title>%s</title></head></html>' %
400 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000401
402 return True
403
404 def CacheExpiresHandler(self):
405 """This request handler yields a page with the title set to the current
406 system time, and set the page to expire on 1 Jan 2099."""
407
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000408 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000409 return False
410
411 self.send_response(200)
412 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000413 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000414 self.end_headers()
415
maruel@google.come250a9b2009-03-10 17:39:46 +0000416 self.wfile.write('<html><head><title>%s</title></head></html>' %
417 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000418
419 return True
420
421 def CacheProxyRevalidateHandler(self):
422 """This request handler yields a page with the title set to the current
423 system time, and allows caching for 60 seconds"""
424
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000425 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000426 return False
427
428 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000429 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000430 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
431 self.end_headers()
432
maruel@google.come250a9b2009-03-10 17:39:46 +0000433 self.wfile.write('<html><head><title>%s</title></head></html>' %
434 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000435
436 return True
437
438 def CachePrivateHandler(self):
439 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700440 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000441
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000442 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000443 return False
444
445 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000446 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000447 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.end_headers()
449
maruel@google.come250a9b2009-03-10 17:39:46 +0000450 self.wfile.write('<html><head><title>%s</title></head></html>' %
451 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000452
453 return True
454
455 def CachePublicHandler(self):
456 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700457 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000458
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000459 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000460 return False
461
462 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000463 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000464 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000465 self.end_headers()
466
maruel@google.come250a9b2009-03-10 17:39:46 +0000467 self.wfile.write('<html><head><title>%s</title></head></html>' %
468 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000469
470 return True
471
472 def CacheSMaxAgeHandler(self):
473 """This request handler yields a page with the title set to the current
474 system time, and does not allow for caching."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000480 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
482 self.end_headers()
483
maruel@google.come250a9b2009-03-10 17:39:46 +0000484 self.wfile.write('<html><head><title>%s</title></head></html>' %
485 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000486
487 return True
488
489 def CacheMustRevalidateHandler(self):
490 """This request handler yields a page with the title set to the current
491 system time, and does not allow caching."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000497 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000498 self.send_header('Cache-Control', 'must-revalidate')
499 self.end_headers()
500
maruel@google.come250a9b2009-03-10 17:39:46 +0000501 self.wfile.write('<html><head><title>%s</title></head></html>' %
502 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000503
504 return True
505
506 def CacheMustRevalidateMaxAgeHandler(self):
507 """This request handler yields a page with the title set to the current
508 system time, and does not allow caching event though max-age of 60
509 seconds is specified."""
510
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000511 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000512 return False
513
514 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000516 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
517 self.end_headers()
518
maruel@google.come250a9b2009-03-10 17:39:46 +0000519 self.wfile.write('<html><head><title>%s</title></head></html>' %
520 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000521
522 return True
523
initial.commit94958cf2008-07-26 22:42:52 +0000524 def CacheNoStoreHandler(self):
525 """This request handler yields a page with the title set to the current
526 system time, and does not allow the page to be stored."""
527
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000528 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000529 return False
530
531 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000532 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000533 self.send_header('Cache-Control', 'no-store')
534 self.end_headers()
535
maruel@google.come250a9b2009-03-10 17:39:46 +0000536 self.wfile.write('<html><head><title>%s</title></head></html>' %
537 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000538
539 return True
540
541 def CacheNoStoreMaxAgeHandler(self):
542 """This request handler yields a page with the title set to the current
543 system time, and does not allow the page to be stored even though max-age
544 of 60 seconds is specified."""
545
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000546 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000547 return False
548
549 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000550 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000551 self.send_header('Cache-Control', 'max-age=60, no-store')
552 self.end_headers()
553
maruel@google.come250a9b2009-03-10 17:39:46 +0000554 self.wfile.write('<html><head><title>%s</title></head></html>' %
555 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000556
557 return True
558
559
560 def CacheNoTransformHandler(self):
561 """This request handler yields a page with the title set to the current
562 system time, and does not allow the content to transformed during
563 user-agent caching"""
564
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000565 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000566 return False
567
568 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000569 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000570 self.send_header('Cache-Control', 'no-transform')
571 self.end_headers()
572
maruel@google.come250a9b2009-03-10 17:39:46 +0000573 self.wfile.write('<html><head><title>%s</title></head></html>' %
574 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000575
576 return True
577
578 def EchoHeader(self):
579 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000580
ananta@chromium.org219b2062009-10-23 16:09:41 +0000581 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000582
ananta@chromium.org56812d02011-04-07 17:52:05 +0000583 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000584 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000585 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000586
ananta@chromium.org56812d02011-04-07 17:52:05 +0000587 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000588
589 def EchoHeaderHelper(self, echo_header):
590 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000591
ananta@chromium.org219b2062009-10-23 16:09:41 +0000592 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000593 return False
594
595 query_char = self.path.find('?')
596 if query_char != -1:
597 header_name = self.path[query_char+1:]
598
599 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000600 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000601 if echo_header == '/echoheadercache':
602 self.send_header('Cache-control', 'max-age=60000')
603 else:
604 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000605 # insert a vary header to properly indicate that the cachability of this
606 # request is subject to value of the request header being echoed.
607 if len(header_name) > 0:
608 self.send_header('Vary', header_name)
609 self.end_headers()
610
611 if len(header_name) > 0:
612 self.wfile.write(self.headers.getheader(header_name))
613
614 return True
615
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000616 def ReadRequestBody(self):
617 """This function reads the body of the current HTTP request, handling
618 both plain and chunked transfer encoded requests."""
619
620 if self.headers.getheader('transfer-encoding') != 'chunked':
621 length = int(self.headers.getheader('content-length'))
622 return self.rfile.read(length)
623
624 # Read the request body as chunks.
625 body = ""
626 while True:
627 line = self.rfile.readline()
628 length = int(line, 16)
629 if length == 0:
630 self.rfile.readline()
631 break
632 body += self.rfile.read(length)
633 self.rfile.read(2)
634 return body
635
initial.commit94958cf2008-07-26 22:42:52 +0000636 def EchoHandler(self):
637 """This handler just echoes back the payload of the request, for testing
638 form submission."""
639
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000640 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000641 return False
642
hirono2838c572015-01-21 12:18:11 -0800643 _, _, _, _, query, _ = urlparse.urlparse(self.path)
644 query_params = cgi.parse_qs(query, True)
645 if 'status' in query_params:
646 self.send_response(int(query_params['status'][0]))
647 else:
648 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000649 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000650 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000651 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000652 return True
653
654 def EchoTitleHandler(self):
655 """This handler is like Echo, but sets the page title to the request."""
656
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000657 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000658 return False
659
660 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000661 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000662 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000663 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000664 self.wfile.write('<html><head><title>')
665 self.wfile.write(request)
666 self.wfile.write('</title></head></html>')
667 return True
668
669 def EchoAllHandler(self):
670 """This handler yields a (more) human-readable page listing information
671 about the request header & contents."""
672
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000673 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000674 return False
675
676 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000677 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000678 self.end_headers()
679 self.wfile.write('<html><head><style>'
680 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
681 '</style></head><body>'
682 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000683 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000684 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000685
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000686 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000687 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000688 params = cgi.parse_qs(qs, keep_blank_values=1)
689
690 for param in params:
691 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000692
693 self.wfile.write('</pre>')
694
695 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
696
697 self.wfile.write('</body></html>')
698 return True
699
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000700 def EchoMultipartPostHandler(self):
701 """This handler echoes received multipart post data as json format."""
702
703 if not (self._ShouldHandleRequest("/echomultipartpost") or
704 self._ShouldHandleRequest("/searchbyimage")):
705 return False
706
707 content_type, parameters = cgi.parse_header(
708 self.headers.getheader('content-type'))
709 if content_type == 'multipart/form-data':
710 post_multipart = cgi.parse_multipart(self.rfile, parameters)
711 elif content_type == 'application/x-www-form-urlencoded':
712 raise Exception('POST by application/x-www-form-urlencoded is '
713 'not implemented.')
714 else:
715 post_multipart = {}
716
717 # Since the data can be binary, we encode them by base64.
718 post_multipart_base64_encoded = {}
719 for field, values in post_multipart.items():
720 post_multipart_base64_encoded[field] = [base64.b64encode(value)
721 for value in values]
722
723 result = {'POST_multipart' : post_multipart_base64_encoded}
724
725 self.send_response(200)
726 self.send_header("Content-type", "text/plain")
727 self.end_headers()
728 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
729 return True
730
initial.commit94958cf2008-07-26 22:42:52 +0000731 def DownloadHandler(self):
732 """This handler sends a downloadable file with or without reporting
733 the size (6K)."""
734
735 if self.path.startswith("/download-unknown-size"):
736 send_length = False
737 elif self.path.startswith("/download-known-size"):
738 send_length = True
739 else:
740 return False
741
742 #
743 # The test which uses this functionality is attempting to send
744 # small chunks of data to the client. Use a fairly large buffer
745 # so that we'll fill chrome's IO buffer enough to force it to
746 # actually write the data.
747 # See also the comments in the client-side of this test in
748 # download_uitest.cc
749 #
750 size_chunk1 = 35*1024
751 size_chunk2 = 10*1024
752
753 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000754 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000755 self.send_header('Cache-Control', 'max-age=0')
756 if send_length:
757 self.send_header('Content-Length', size_chunk1 + size_chunk2)
758 self.end_headers()
759
760 # First chunk of data:
761 self.wfile.write("*" * size_chunk1)
762 self.wfile.flush()
763
764 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000765 self.server.wait_for_download = True
766 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000767 self.server.handle_request()
768
769 # Second chunk of data:
770 self.wfile.write("*" * size_chunk2)
771 return True
772
773 def DownloadFinishHandler(self):
774 """This handler just tells the server to finish the current download."""
775
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000776 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000777 return False
778
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000779 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000780 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000781 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000782 self.send_header('Cache-Control', 'max-age=0')
783 self.end_headers()
784 return True
785
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000786 def _ReplaceFileData(self, data, query_parameters):
787 """Replaces matching substrings in a file.
788
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000789 If the 'replace_text' URL query parameter is present, it is expected to be
790 of the form old_text:new_text, which indicates that any old_text strings in
791 the file are replaced with new_text. Multiple 'replace_text' parameters may
792 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000793
794 If the parameters are not present, |data| is returned.
795 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000796
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000797 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000798 replace_text_values = query_dict.get('replace_text', [])
799 for replace_text_value in replace_text_values:
800 replace_text_args = replace_text_value.split(':')
801 if len(replace_text_args) != 2:
802 raise ValueError(
803 'replace_text must be of form old_text:new_text. Actual value: %s' %
804 replace_text_value)
805 old_text_b64, new_text_b64 = replace_text_args
806 old_text = base64.urlsafe_b64decode(old_text_b64)
807 new_text = base64.urlsafe_b64decode(new_text_b64)
808 data = data.replace(old_text, new_text)
809 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000810
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000811 def ZipFileHandler(self):
812 """This handler sends the contents of the requested file in compressed form.
813 Can pass in a parameter that specifies that the content length be
814 C - the compressed size (OK),
815 U - the uncompressed size (Non-standard, but handled),
816 S - less than compressed (OK because we keep going),
817 M - larger than compressed but less than uncompressed (an error),
818 L - larger than uncompressed (an error)
819 Example: compressedfiles/Picture_1.doc?C
820 """
821
822 prefix = "/compressedfiles/"
823 if not self.path.startswith(prefix):
824 return False
825
826 # Consume a request body if present.
827 if self.command == 'POST' or self.command == 'PUT' :
828 self.ReadRequestBody()
829
830 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
831
832 if not query in ('C', 'U', 'S', 'M', 'L'):
833 return False
834
835 sub_path = url_path[len(prefix):]
836 entries = sub_path.split('/')
837 file_path = os.path.join(self.server.data_dir, *entries)
838 if os.path.isdir(file_path):
839 file_path = os.path.join(file_path, 'index.html')
840
841 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000842 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000843 self.send_error(404)
844 return True
845
846 f = open(file_path, "rb")
847 data = f.read()
848 uncompressed_len = len(data)
849 f.close()
850
851 # Compress the data.
852 data = zlib.compress(data)
853 compressed_len = len(data)
854
855 content_length = compressed_len
856 if query == 'U':
857 content_length = uncompressed_len
858 elif query == 'S':
859 content_length = compressed_len / 2
860 elif query == 'M':
861 content_length = (compressed_len + uncompressed_len) / 2
862 elif query == 'L':
863 content_length = compressed_len + uncompressed_len
864
865 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000866 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000867 self.send_header('Content-encoding', 'deflate')
868 self.send_header('Connection', 'close')
869 self.send_header('Content-Length', content_length)
870 self.send_header('ETag', '\'' + file_path + '\'')
871 self.end_headers()
872
873 self.wfile.write(data)
874
875 return True
876
initial.commit94958cf2008-07-26 22:42:52 +0000877 def FileHandler(self):
878 """This handler sends the contents of the requested file. Wow, it's like
879 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000880
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000881 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000882 if not self.path.startswith(prefix):
883 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000884 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000885
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000886 def PostOnlyFileHandler(self):
887 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000888
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000889 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000890 if not self.path.startswith(prefix):
891 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000892 return self._FileHandlerHelper(prefix)
893
894 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000895 request_body = ''
896 if self.command == 'POST' or self.command == 'PUT':
897 # Consume a request body if present.
898 request_body = self.ReadRequestBody()
899
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000900 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000901 query_dict = cgi.parse_qs(query)
902
903 expected_body = query_dict.get('expected_body', [])
904 if expected_body and request_body not in expected_body:
905 self.send_response(404)
906 self.end_headers()
907 self.wfile.write('')
908 return True
909
910 expected_headers = query_dict.get('expected_headers', [])
911 for expected_header in expected_headers:
912 header_name, expected_value = expected_header.split(':')
913 if self.headers.getheader(header_name) != expected_value:
914 self.send_response(404)
915 self.end_headers()
916 self.wfile.write('')
917 return True
918
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000919 sub_path = url_path[len(prefix):]
920 entries = sub_path.split('/')
921 file_path = os.path.join(self.server.data_dir, *entries)
922 if os.path.isdir(file_path):
923 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000924
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000925 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000926 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000927 self.send_error(404)
928 return True
929
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000930 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000931 data = f.read()
932 f.close()
933
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000934 data = self._ReplaceFileData(data, query)
935
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000936 old_protocol_version = self.protocol_version
937
initial.commit94958cf2008-07-26 22:42:52 +0000938 # If file.mock-http-headers exists, it contains the headers we
939 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000940 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000941 if os.path.isfile(headers_path):
942 f = open(headers_path, "r")
943
944 # "HTTP/1.1 200 OK"
945 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000946 http_major, http_minor, status_code = re.findall(
947 'HTTP/(\d+).(\d+) (\d+)', response)[0]
948 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000949 self.send_response(int(status_code))
950
951 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000952 header_values = re.findall('(\S+):\s*(.*)', line)
953 if len(header_values) > 0:
954 # "name: value"
955 name, value = header_values[0]
956 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000957 f.close()
958 else:
959 # Could be more generic once we support mime-type sniffing, but for
960 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000961
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000962 range_header = self.headers.get('Range')
963 if range_header and range_header.startswith('bytes='):
964 # Note this doesn't handle all valid byte range_header values (i.e.
965 # left open ended ones), just enough for what we needed so far.
966 range_header = range_header[6:].split('-')
967 start = int(range_header[0])
968 if range_header[1]:
969 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000970 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000971 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000972
973 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000974 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
975 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +0000976 self.send_header('Content-Range', content_range)
977 data = data[start: end + 1]
978 else:
979 self.send_response(200)
980
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000981 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +0000982 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +0000983 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +0000984 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +0000985 self.end_headers()
986
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000987 if (self.command != 'HEAD'):
988 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +0000989
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000990 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +0000991 return True
992
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000993 def SetCookieHandler(self):
994 """This handler just sets a cookie, for testing cookie handling."""
995
996 if not self._ShouldHandleRequest("/set-cookie"):
997 return False
998
999 query_char = self.path.find('?')
1000 if query_char != -1:
1001 cookie_values = self.path[query_char + 1:].split('&')
1002 else:
1003 cookie_values = ("",)
1004 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001005 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001006 for cookie_value in cookie_values:
1007 self.send_header('Set-Cookie', '%s' % cookie_value)
1008 self.end_headers()
1009 for cookie_value in cookie_values:
1010 self.wfile.write('%s' % cookie_value)
1011 return True
1012
mattm@chromium.org983fc462012-06-30 00:52:08 +00001013 def ExpectAndSetCookieHandler(self):
1014 """Expects some cookies to be sent, and if they are, sets more cookies.
1015
1016 The expect parameter specifies a required cookie. May be specified multiple
1017 times.
1018 The set parameter specifies a cookie to set if all required cookies are
1019 preset. May be specified multiple times.
1020 The data parameter specifies the response body data to be returned."""
1021
1022 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1023 return False
1024
1025 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1026 query_dict = cgi.parse_qs(query)
1027 cookies = set()
1028 if 'Cookie' in self.headers:
1029 cookie_header = self.headers.getheader('Cookie')
1030 cookies.update([s.strip() for s in cookie_header.split(';')])
1031 got_all_expected_cookies = True
1032 for expected_cookie in query_dict.get('expect', []):
1033 if expected_cookie not in cookies:
1034 got_all_expected_cookies = False
1035 self.send_response(200)
1036 self.send_header('Content-Type', 'text/html')
1037 if got_all_expected_cookies:
1038 for cookie_value in query_dict.get('set', []):
1039 self.send_header('Set-Cookie', '%s' % cookie_value)
1040 self.end_headers()
1041 for data_value in query_dict.get('data', []):
1042 self.wfile.write(data_value)
1043 return True
1044
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001045 def SetHeaderHandler(self):
1046 """This handler sets a response header. Parameters are in the
1047 key%3A%20value&key2%3A%20value2 format."""
1048
1049 if not self._ShouldHandleRequest("/set-header"):
1050 return False
1051
1052 query_char = self.path.find('?')
1053 if query_char != -1:
1054 headers_values = self.path[query_char + 1:].split('&')
1055 else:
1056 headers_values = ("",)
1057 self.send_response(200)
1058 self.send_header('Content-Type', 'text/html')
1059 for header_value in headers_values:
1060 header_value = urllib.unquote(header_value)
1061 (key, value) = header_value.split(': ', 1)
1062 self.send_header(key, value)
1063 self.end_headers()
1064 for header_value in headers_values:
1065 self.wfile.write('%s' % header_value)
1066 return True
1067
initial.commit94958cf2008-07-26 22:42:52 +00001068 def AuthBasicHandler(self):
1069 """This handler tests 'Basic' authentication. It just sends a page with
1070 title 'user/pass' if you succeed."""
1071
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001072 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001073 return False
1074
1075 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001076 expected_password = 'secret'
1077 realm = 'testrealm'
1078 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001079
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001080 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1081 query_params = cgi.parse_qs(query, True)
1082 if 'set-cookie-if-challenged' in query_params:
1083 set_cookie_if_challenged = True
1084 if 'password' in query_params:
1085 expected_password = query_params['password'][0]
1086 if 'realm' in query_params:
1087 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001088
initial.commit94958cf2008-07-26 22:42:52 +00001089 auth = self.headers.getheader('authorization')
1090 try:
1091 if not auth:
1092 raise Exception('no auth')
1093 b64str = re.findall(r'Basic (\S+)', auth)[0]
1094 userpass = base64.b64decode(b64str)
1095 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001096 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001097 raise Exception('wrong password')
1098 except Exception, e:
1099 # Authentication failed.
1100 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001101 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001102 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001103 if set_cookie_if_challenged:
1104 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001105 self.end_headers()
1106 self.wfile.write('<html><head>')
1107 self.wfile.write('<title>Denied: %s</title>' % e)
1108 self.wfile.write('</head><body>')
1109 self.wfile.write('auth=%s<p>' % auth)
1110 self.wfile.write('b64str=%s<p>' % b64str)
1111 self.wfile.write('username: %s<p>' % username)
1112 self.wfile.write('userpass: %s<p>' % userpass)
1113 self.wfile.write('password: %s<p>' % password)
1114 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1115 self.wfile.write('</body></html>')
1116 return True
1117
1118 # Authentication successful. (Return a cachable response to allow for
1119 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001120 old_protocol_version = self.protocol_version
1121 self.protocol_version = "HTTP/1.1"
1122
initial.commit94958cf2008-07-26 22:42:52 +00001123 if_none_match = self.headers.getheader('if-none-match')
1124 if if_none_match == "abc":
1125 self.send_response(304)
1126 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001127 elif url_path.endswith(".gif"):
1128 # Using chrome/test/data/google/logo.gif as the test image
1129 test_image_path = ['google', 'logo.gif']
1130 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1131 if not os.path.isfile(gif_path):
1132 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001133 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001134 return True
1135
1136 f = open(gif_path, "rb")
1137 data = f.read()
1138 f.close()
1139
1140 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001141 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001142 self.send_header('Cache-control', 'max-age=60000')
1143 self.send_header('Etag', 'abc')
1144 self.end_headers()
1145 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001146 else:
1147 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001148 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001149 self.send_header('Cache-control', 'max-age=60000')
1150 self.send_header('Etag', 'abc')
1151 self.end_headers()
1152 self.wfile.write('<html><head>')
1153 self.wfile.write('<title>%s/%s</title>' % (username, password))
1154 self.wfile.write('</head><body>')
1155 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001156 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001157 self.wfile.write('</body></html>')
1158
rvargas@google.com54453b72011-05-19 01:11:11 +00001159 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001160 return True
1161
tonyg@chromium.org75054202010-03-31 22:06:10 +00001162 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001163 """Returns a nonce that's stable per request path for the server's lifetime.
1164 This is a fake implementation. A real implementation would only use a given
1165 nonce a single time (hence the name n-once). However, for the purposes of
1166 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001167
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001168 Args:
1169 force_reset: Iff set, the nonce will be changed. Useful for testing the
1170 "stale" response.
1171 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001172
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001173 if force_reset or not self.server.nonce_time:
1174 self.server.nonce_time = time.time()
1175 return hashlib.md5('privatekey%s%d' %
1176 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001177
1178 def AuthDigestHandler(self):
1179 """This handler tests 'Digest' authentication.
1180
1181 It just sends a page with title 'user/pass' if you succeed.
1182
1183 A stale response is sent iff "stale" is present in the request path.
1184 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001185
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001186 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001187 return False
1188
tonyg@chromium.org75054202010-03-31 22:06:10 +00001189 stale = 'stale' in self.path
1190 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001191 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001192 password = 'secret'
1193 realm = 'testrealm'
1194
1195 auth = self.headers.getheader('authorization')
1196 pairs = {}
1197 try:
1198 if not auth:
1199 raise Exception('no auth')
1200 if not auth.startswith('Digest'):
1201 raise Exception('not digest')
1202 # Pull out all the name="value" pairs as a dictionary.
1203 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1204
1205 # Make sure it's all valid.
1206 if pairs['nonce'] != nonce:
1207 raise Exception('wrong nonce')
1208 if pairs['opaque'] != opaque:
1209 raise Exception('wrong opaque')
1210
1211 # Check the 'response' value and make sure it matches our magic hash.
1212 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001213 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001214 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001215 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001216 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001217 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001218 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1219 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001220 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001221
1222 if pairs['response'] != response:
1223 raise Exception('wrong password')
1224 except Exception, e:
1225 # Authentication failed.
1226 self.send_response(401)
1227 hdr = ('Digest '
1228 'realm="%s", '
1229 'domain="/", '
1230 'qop="auth", '
1231 'algorithm=MD5, '
1232 'nonce="%s", '
1233 'opaque="%s"') % (realm, nonce, opaque)
1234 if stale:
1235 hdr += ', stale="TRUE"'
1236 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001237 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001238 self.end_headers()
1239 self.wfile.write('<html><head>')
1240 self.wfile.write('<title>Denied: %s</title>' % e)
1241 self.wfile.write('</head><body>')
1242 self.wfile.write('auth=%s<p>' % auth)
1243 self.wfile.write('pairs=%s<p>' % pairs)
1244 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1245 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1246 self.wfile.write('</body></html>')
1247 return True
1248
1249 # Authentication successful.
1250 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001251 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001252 self.end_headers()
1253 self.wfile.write('<html><head>')
1254 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1255 self.wfile.write('</head><body>')
1256 self.wfile.write('auth=%s<p>' % auth)
1257 self.wfile.write('pairs=%s<p>' % pairs)
1258 self.wfile.write('</body></html>')
1259
1260 return True
1261
1262 def SlowServerHandler(self):
1263 """Wait for the user suggested time before responding. The syntax is
1264 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001265
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001266 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001267 return False
1268 query_char = self.path.find('?')
1269 wait_sec = 1.0
1270 if query_char >= 0:
1271 try:
davidben05f82202015-03-31 13:48:07 -07001272 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001273 except ValueError:
1274 pass
1275 time.sleep(wait_sec)
1276 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001277 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001278 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001279 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001280 return True
1281
creis@google.com2f4f6a42011-03-25 19:44:19 +00001282 def NoContentHandler(self):
1283 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001284
creis@google.com2f4f6a42011-03-25 19:44:19 +00001285 if not self._ShouldHandleRequest("/nocontent"):
1286 return False
1287 self.send_response(204)
1288 self.end_headers()
1289 return True
1290
initial.commit94958cf2008-07-26 22:42:52 +00001291 def ServerRedirectHandler(self):
1292 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001293 '/server-redirect?http://foo.bar/asdf' to redirect to
1294 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001295
1296 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001297 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001298 return False
1299
1300 query_char = self.path.find('?')
1301 if query_char < 0 or len(self.path) <= query_char + 1:
1302 self.sendRedirectHelp(test_name)
1303 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001304 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001305
1306 self.send_response(301) # moved permanently
1307 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001308 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001309 self.end_headers()
1310 self.wfile.write('<html><head>')
1311 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1312
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001313 return True
initial.commit94958cf2008-07-26 22:42:52 +00001314
naskoe7a0d0d2014-09-29 08:53:05 -07001315 def CrossSiteRedirectHandler(self):
1316 """Sends a server redirect to the given site. The syntax is
1317 '/cross-site/hostname/...' to redirect to //hostname/...
1318 It is used to navigate between different Sites, causing
1319 cross-site/cross-process navigations in the browser."""
1320
1321 test_name = "/cross-site"
1322 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001323 return False
1324
1325 params = urllib.unquote(self.path[(len(test_name) + 1):])
1326 slash = params.find('/')
1327 if slash < 0:
1328 self.sendRedirectHelp(test_name)
1329 return True
1330
1331 host = params[:slash]
1332 path = params[(slash+1):]
1333 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1334
1335 self.send_response(301) # moved permanently
1336 self.send_header('Location', dest)
1337 self.send_header('Content-Type', 'text/html')
1338 self.end_headers()
1339 self.wfile.write('<html><head>')
1340 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1341
1342 return True
1343
initial.commit94958cf2008-07-26 22:42:52 +00001344 def ClientRedirectHandler(self):
1345 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001346 '/client-redirect?http://foo.bar/asdf' to redirect to
1347 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001348
1349 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001350 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001351 return False
1352
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001353 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001354 if query_char < 0 or len(self.path) <= query_char + 1:
1355 self.sendRedirectHelp(test_name)
1356 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001357 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001358
1359 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001360 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001361 self.end_headers()
1362 self.wfile.write('<html><head>')
1363 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1364 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1365
1366 return True
1367
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001368 def GetSSLSessionCacheHandler(self):
1369 """Send a reply containing a log of the session cache operations."""
1370
1371 if not self._ShouldHandleRequest('/ssl-session-cache'):
1372 return False
1373
1374 self.send_response(200)
1375 self.send_header('Content-Type', 'text/plain')
1376 self.end_headers()
1377 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001378 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001379 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001380 self.wfile.write('Pass --https-record-resume in order to use' +
1381 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001382 return True
1383
1384 for (action, sessionID) in log:
1385 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001386 return True
1387
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001388 def SSLManySmallRecords(self):
1389 """Sends a reply consisting of a variety of small writes. These will be
1390 translated into a series of small SSL records when used over an HTTPS
1391 server."""
1392
1393 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1394 return False
1395
1396 self.send_response(200)
1397 self.send_header('Content-Type', 'text/plain')
1398 self.end_headers()
1399
1400 # Write ~26K of data, in 1350 byte chunks
1401 for i in xrange(20):
1402 self.wfile.write('*' * 1350)
1403 self.wfile.flush()
1404 return True
1405
agl@chromium.org04700be2013-03-02 18:40:41 +00001406 def GetChannelID(self):
1407 """Send a reply containing the hashed ChannelID that the client provided."""
1408
1409 if not self._ShouldHandleRequest('/channel-id'):
1410 return False
1411
1412 self.send_response(200)
1413 self.send_header('Content-Type', 'text/plain')
1414 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001415 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001416 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1417 return True
1418
pneubeckfd4f0442015-08-07 04:55:10 -07001419 def GetClientCert(self):
1420 """Send a reply whether a client certificate was provided."""
1421
1422 if not self._ShouldHandleRequest('/client-cert'):
1423 return False
1424
1425 self.send_response(200)
1426 self.send_header('Content-Type', 'text/plain')
1427 self.end_headers()
1428
1429 cert_chain = self.server.tlsConnection.session.clientCertChain
1430 if cert_chain != None:
1431 self.wfile.write('got client cert with fingerprint: ' +
1432 cert_chain.getFingerprint())
1433 else:
1434 self.wfile.write('got no client cert')
1435 return True
1436
davidben599e7e72014-09-03 16:19:09 -07001437 def ClientCipherListHandler(self):
1438 """Send a reply containing the cipher suite list that the client
1439 provided. Each cipher suite value is serialized in decimal, followed by a
1440 newline."""
1441
1442 if not self._ShouldHandleRequest('/client-cipher-list'):
1443 return False
1444
1445 self.send_response(200)
1446 self.send_header('Content-Type', 'text/plain')
1447 self.end_headers()
1448
davidben11682512014-10-06 21:09:11 -07001449 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1450 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001451 return True
1452
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001453 def CloseSocketHandler(self):
1454 """Closes the socket without sending anything."""
1455
1456 if not self._ShouldHandleRequest('/close-socket'):
1457 return False
1458
1459 self.wfile.close()
1460 return True
1461
initial.commit94958cf2008-07-26 22:42:52 +00001462 def DefaultResponseHandler(self):
1463 """This is the catch-all response handler for requests that aren't handled
1464 by one of the special handlers above.
1465 Note that we specify the content-length as without it the https connection
1466 is not closed properly (and the browser keeps expecting data)."""
1467
1468 contents = "Default response given for path: " + self.path
1469 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001470 self.send_header('Content-Type', 'text/html')
1471 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001472 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001473 if (self.command != 'HEAD'):
1474 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001475 return True
1476
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001477 def RedirectConnectHandler(self):
1478 """Sends a redirect to the CONNECT request for www.redirect.com. This
1479 response is not specified by the RFC, so the browser should not follow
1480 the redirect."""
1481
1482 if (self.path.find("www.redirect.com") < 0):
1483 return False
1484
1485 dest = "http://www.destination.com/foo.js"
1486
1487 self.send_response(302) # moved temporarily
1488 self.send_header('Location', dest)
1489 self.send_header('Connection', 'close')
1490 self.end_headers()
1491 return True
1492
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001493 def ServerAuthConnectHandler(self):
1494 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1495 response doesn't make sense because the proxy server cannot request
1496 server authentication."""
1497
1498 if (self.path.find("www.server-auth.com") < 0):
1499 return False
1500
1501 challenge = 'Basic realm="WallyWorld"'
1502
1503 self.send_response(401) # unauthorized
1504 self.send_header('WWW-Authenticate', challenge)
1505 self.send_header('Connection', 'close')
1506 self.end_headers()
1507 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001508
1509 def DefaultConnectResponseHandler(self):
1510 """This is the catch-all response handler for CONNECT requests that aren't
1511 handled by one of the special handlers above. Real Web servers respond
1512 with 400 to CONNECT requests."""
1513
1514 contents = "Your client has issued a malformed or illegal request."
1515 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001516 self.send_header('Content-Type', 'text/html')
1517 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001518 self.end_headers()
1519 self.wfile.write(contents)
1520 return True
1521
initial.commit94958cf2008-07-26 22:42:52 +00001522 # called by the redirect handling function when there is no parameter
1523 def sendRedirectHelp(self, redirect_name):
1524 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001525 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001526 self.end_headers()
1527 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1528 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1529 self.wfile.write('</body></html>')
1530
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001531 # called by chunked handling function
1532 def sendChunkHelp(self, chunk):
1533 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1534 self.wfile.write('%X\r\n' % len(chunk))
1535 self.wfile.write(chunk)
1536 self.wfile.write('\r\n')
1537
akalin@chromium.org154bb132010-11-12 02:20:27 +00001538
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001539class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001540 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001541 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001542 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001543 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001544 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001545 testserver_base.BasePageHandler.__init__(self, request, client_address,
1546 socket_server, [], handlers, [],
1547 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001548
1549 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001550 if self._ShouldHandleRequest("/ocsp"):
1551 response = self.ocsp_response
1552 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1553 response = self.ocsp_response_intermediate
1554 else:
mattm10ede842016-11-29 11:57:16 -08001555 return False
1556 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001557 self.send_response(200)
1558 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001559 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001560 self.end_headers()
1561
Matt Mueller55aef642018-05-02 18:53:57 +00001562 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001563
mattm10ede842016-11-29 11:57:16 -08001564 def CaIssuersResponse(self):
1565 if not self._ShouldHandleRequest("/ca_issuers"):
1566 return False
1567 print 'handling ca_issuers request'
1568 self.send_response(200)
1569 self.send_header('Content-Type', 'application/pkix-cert')
1570 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1571 self.end_headers()
1572
1573 self.wfile.write(self.ca_issuers_response)
1574
mattm@chromium.org830a3712012-11-07 23:00:07 +00001575
Adam Rice9476b8c2018-08-02 15:28:43 +00001576class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1577 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1578 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001579 """
1580
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001581 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001582
bashi@chromium.org33233532012-09-08 17:37:24 +00001583 def _start_read_write(self, sock):
1584 sock.setblocking(0)
1585 self.request.setblocking(0)
1586 rlist = [self.request, sock]
1587 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001588 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001589 if errors:
1590 self.send_response(500)
1591 self.end_headers()
1592 return
1593 for s in ready_sockets:
1594 received = s.recv(1024)
1595 if len(received) == 0:
1596 return
1597 if s == self.request:
1598 other = sock
1599 else:
1600 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001601 # This will lose data if the kernel write buffer fills up.
1602 # TODO(ricea): Correctly use the return value to track how much was
1603 # written and buffer the rest. Use select to determine when the socket
1604 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001605 other.send(received)
1606
1607 def _do_common_method(self):
1608 url = urlparse.urlparse(self.path)
1609 port = url.port
1610 if not port:
1611 if url.scheme == 'http':
1612 port = 80
1613 elif url.scheme == 'https':
1614 port = 443
1615 if not url.hostname or not port:
1616 self.send_response(400)
1617 self.end_headers()
1618 return
1619
1620 if len(url.path) == 0:
1621 path = '/'
1622 else:
1623 path = url.path
1624 if len(url.query) > 0:
1625 path = '%s?%s' % (url.path, url.query)
1626
1627 sock = None
1628 try:
1629 sock = socket.create_connection((url.hostname, port))
1630 sock.send('%s %s %s\r\n' % (
1631 self.command, path, self.protocol_version))
1632 for header in self.headers.headers:
1633 header = header.strip()
1634 if (header.lower().startswith('connection') or
1635 header.lower().startswith('proxy')):
1636 continue
1637 sock.send('%s\r\n' % header)
1638 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001639 # This is wrong: it will pass through connection-level headers and
1640 # misbehave on connection reuse. The only reason it works at all is that
1641 # our test servers have never supported connection reuse.
1642 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001643 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001644 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001645 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001646 self.send_response(500)
1647 self.end_headers()
1648 finally:
1649 if sock is not None:
1650 sock.close()
1651
1652 def do_CONNECT(self):
1653 try:
1654 pos = self.path.rfind(':')
1655 host = self.path[:pos]
1656 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001657 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001658 self.send_response(400)
1659 self.end_headers()
1660
Adam Rice9476b8c2018-08-02 15:28:43 +00001661 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001662 host = "127.0.0.1"
1663
Adam Rice54443aa2018-06-06 00:11:54 +00001664 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001665 try:
1666 sock = socket.create_connection((host, port))
1667 self.send_response(200, 'Connection established')
1668 self.end_headers()
1669 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001670 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001671 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001672 self.send_response(500)
1673 self.end_headers()
1674 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001675 if sock is not None:
1676 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001677
1678 def do_GET(self):
1679 self._do_common_method()
1680
1681 def do_HEAD(self):
1682 self._do_common_method()
1683
Adam Rice9476b8c2018-08-02 15:28:43 +00001684class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1685 """A request handler that behaves as a proxy server which requires
1686 basic authentication.
1687 """
1688
1689 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1690
1691 def parse_request(self):
1692 """Overrides parse_request to check credential."""
1693
1694 if not ProxyRequestHandler.parse_request(self):
1695 return False
1696
1697 auth = self.headers.getheader('Proxy-Authorization')
1698 if auth != self._AUTH_CREDENTIAL:
1699 self.send_response(407)
1700 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1701 self.end_headers()
1702 return False
1703
1704 return True
1705
bashi@chromium.org33233532012-09-08 17:37:24 +00001706
mattm@chromium.org830a3712012-11-07 23:00:07 +00001707class ServerRunner(testserver_base.TestServerRunner):
1708 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001709
mattm@chromium.org830a3712012-11-07 23:00:07 +00001710 def __init__(self):
1711 super(ServerRunner, self).__init__()
1712 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001713
mattm@chromium.org830a3712012-11-07 23:00:07 +00001714 def __make_data_dir(self):
1715 if self.options.data_dir:
1716 if not os.path.isdir(self.options.data_dir):
1717 raise testserver_base.OptionError('specified data dir not found: ' +
1718 self.options.data_dir + ' exiting...')
1719 my_data_dir = self.options.data_dir
1720 else:
1721 # Create the default path to our data dir, relative to the exe dir.
Asanka Herath0ec37152019-08-02 15:23:57 +00001722 my_data_dir = os.path.join(BASE_DIR, "..", "..", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001723
mattm@chromium.org830a3712012-11-07 23:00:07 +00001724 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001725
Matt Mueller55aef642018-05-02 18:53:57 +00001726 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1727 if states_option is None:
1728 return None, None, None
1729
1730 ocsp_states = list()
1731 for ocsp_state_arg in states_option.split(':'):
1732 if ocsp_state_arg == 'ok':
1733 ocsp_state = minica.OCSP_STATE_GOOD
1734 elif ocsp_state_arg == 'revoked':
1735 ocsp_state = minica.OCSP_STATE_REVOKED
1736 elif ocsp_state_arg == 'invalid':
1737 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1738 elif ocsp_state_arg == 'unauthorized':
1739 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1740 elif ocsp_state_arg == 'unknown':
1741 ocsp_state = minica.OCSP_STATE_UNKNOWN
1742 elif ocsp_state_arg == 'later':
1743 ocsp_state = minica.OCSP_STATE_TRY_LATER
1744 elif ocsp_state_arg == 'invalid_data':
1745 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1746 elif ocsp_state_arg == "mismatched_serial":
1747 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1748 else:
1749 raise testserver_base.OptionError('unknown OCSP status: ' +
1750 ocsp_state_arg)
1751 ocsp_states.append(ocsp_state)
1752
1753 if len(ocsp_states) > 1:
1754 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1755 raise testserver_base.OptionError('Multiple OCSP responses '
1756 'incompatible with states ' + str(ocsp_states))
1757
1758 ocsp_dates = list()
1759 for ocsp_date_arg in date_option.split(':'):
1760 if ocsp_date_arg == 'valid':
1761 ocsp_date = minica.OCSP_DATE_VALID
1762 elif ocsp_date_arg == 'old':
1763 ocsp_date = minica.OCSP_DATE_OLD
1764 elif ocsp_date_arg == 'early':
1765 ocsp_date = minica.OCSP_DATE_EARLY
1766 elif ocsp_date_arg == 'long':
1767 ocsp_date = minica.OCSP_DATE_LONG
1768 elif ocsp_date_arg == 'longer':
1769 ocsp_date = minica.OCSP_DATE_LONGER
1770 else:
1771 raise testserver_base.OptionError('unknown OCSP date: ' +
1772 ocsp_date_arg)
1773 ocsp_dates.append(ocsp_date)
1774
1775 if len(ocsp_states) != len(ocsp_dates):
1776 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1777 'count')
1778
1779 ocsp_produced = None
1780 if produced_option == 'valid':
1781 ocsp_produced = minica.OCSP_PRODUCED_VALID
1782 elif produced_option == 'before':
1783 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1784 elif produced_option == 'after':
1785 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1786 else:
1787 raise testserver_base.OptionError('unknown OCSP produced: ' +
1788 produced_option)
1789
1790 return ocsp_states, ocsp_dates, ocsp_produced
1791
mattm@chromium.org830a3712012-11-07 23:00:07 +00001792 def create_server(self, server_data):
1793 port = self.options.port
1794 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001795
Adam Rice54443aa2018-06-06 00:11:54 +00001796 logging.basicConfig()
1797
estark21667d62015-04-08 21:00:16 -07001798 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1799 # will result in a call to |getaddrinfo|, which fails with "nodename
1800 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001801 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001802 if self.options.server_type == SERVER_WEBSOCKET and \
1803 host == "localhost" and \
1804 port == 0:
1805 host = "127.0.0.1"
1806
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001807 # Construct the subjectAltNames for any ad-hoc generated certificates.
1808 # As host can be either a DNS name or IP address, attempt to determine
1809 # which it is, so it can be placed in the appropriate SAN.
1810 dns_sans = None
1811 ip_sans = None
1812 ip = None
1813 try:
1814 ip = socket.inet_aton(host)
1815 ip_sans = [ip]
1816 except socket.error:
1817 pass
1818 if ip is None:
1819 dns_sans = [host]
1820
mattm@chromium.org830a3712012-11-07 23:00:07 +00001821 if self.options.server_type == SERVER_HTTP:
1822 if self.options.https:
1823 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001824 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001825 if self.options.cert_and_key_file:
1826 if not os.path.isfile(self.options.cert_and_key_file):
1827 raise testserver_base.OptionError(
1828 'specified server cert file not found: ' +
1829 self.options.cert_and_key_file + ' exiting...')
1830 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001831 elif self.options.aia_intermediate:
1832 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1833 print ('AIA server started on %s:%d...' %
1834 (host, self.__ocsp_server.server_port))
1835
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001836 ocsp_server_port = self.__ocsp_server.server_port
1837 if self.options.ocsp_proxy_port_number != 0:
1838 ocsp_server_port = self.options.ocsp_proxy_port_number
1839 server_data['ocsp_port'] = self.__ocsp_server.server_port
1840
mattm10ede842016-11-29 11:57:16 -08001841 (pem_cert_and_key, intermediate_cert_der) = \
1842 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001843 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001844 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001845 ca_issuers_url =
1846 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08001847 serial = self.options.cert_serial)
1848
1849 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00001850 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08001851 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001852 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001853 # generate a new certificate and run an OCSP server for it.
1854 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001855 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001856 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001857
Matt Mueller55aef642018-05-02 18:53:57 +00001858 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
1859 self.options.ocsp,
1860 self.options.ocsp_date,
1861 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001862
Matt Mueller55aef642018-05-02 18:53:57 +00001863 (ocsp_intermediate_states, ocsp_intermediate_dates,
1864 ocsp_intermediate_produced) = self.__parse_ocsp_options(
1865 self.options.ocsp_intermediate,
1866 self.options.ocsp_intermediate_date,
1867 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001868
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001869 ocsp_server_port = self.__ocsp_server.server_port
1870 if self.options.ocsp_proxy_port_number != 0:
1871 ocsp_server_port = self.options.ocsp_proxy_port_number
1872 server_data['ocsp_port'] = self.__ocsp_server.server_port
1873
Matt Mueller55aef642018-05-02 18:53:57 +00001874 pem_cert_and_key, (ocsp_der,
1875 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00001876 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001877 ip_sans = ip_sans,
1878 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001879 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07001880 ocsp_states = ocsp_states,
1881 ocsp_dates = ocsp_dates,
1882 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00001883 ocsp_intermediate_url = (
1884 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
1885 if ocsp_intermediate_states else None),
1886 ocsp_intermediate_states = ocsp_intermediate_states,
1887 ocsp_intermediate_dates = ocsp_intermediate_dates,
1888 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00001889 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001890
davidben3e2564a2014-11-07 18:51:00 -08001891 if self.options.ocsp_server_unavailable:
1892 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00001893 self.__ocsp_server.ocsp_response_intermediate = \
1894 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08001895 else:
1896 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00001897 self.__ocsp_server.ocsp_response_intermediate = \
1898 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08001899 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001900
1901 for ca_cert in self.options.ssl_client_ca:
1902 if not os.path.isfile(ca_cert):
1903 raise testserver_base.OptionError(
1904 'specified trusted client CA file not found: ' + ca_cert +
1905 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001906
1907 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08001908 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00001909 # TODO(mattm): Staple the intermediate response too (if applicable,
1910 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08001911 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001912
mattm@chromium.org830a3712012-11-07 23:00:07 +00001913 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1914 self.options.ssl_client_auth,
1915 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001916 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001917 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001918 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07001919 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07001920 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001921 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001922 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001923 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001924 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001925 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001926 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07001927 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07001928 self.options.alert_after_handshake,
1929 self.options.disable_channel_id,
David Benjaminf839f1c2018-10-16 06:01:29 +00001930 self.options.disable_extended_master_secret,
1931 self.options.simulate_tls13_downgrade,
1932 self.options.simulate_tls12_downgrade,
1933 self.options.tls_max_version)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001934 print 'HTTPS server started on https://%s:%d...' % \
1935 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001936 else:
1937 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001938 print 'HTTP server started on http://%s:%d...' % \
1939 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940
1941 server.data_dir = self.__make_data_dir()
1942 server.file_root_url = self.options.file_root_url
1943 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001944 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1946 # is required to work correctly. It should be fixed from pywebsocket side.
1947 os.chdir(self.__make_data_dir())
1948 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00001949 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001950 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00001951 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001952 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00001953 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
1954 if not os.path.isfile(key_path):
1955 raise testserver_base.OptionError(
1956 'specified server cert file not found: ' +
1957 self.options.cert_and_key_file + ' exiting...')
1958 websocket_options.private_key = key_path
1959 websocket_options.certificate = key_path
1960
mattm@chromium.org830a3712012-11-07 23:00:07 +00001961 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00001962 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00001963 websocket_options.tls_client_auth = True
1964 if len(self.options.ssl_client_ca) != 1:
1965 raise testserver_base.OptionError(
1966 'one trusted client CA file should be specified')
1967 if not os.path.isfile(self.options.ssl_client_ca[0]):
1968 raise testserver_base.OptionError(
1969 'specified trusted client CA file not found: ' +
1970 self.options.ssl_client_ca[0] + ' exiting...')
1971 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07001972 print 'Trying to start websocket server on %s://%s:%d...' % \
1973 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001974 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001975 print 'WebSocket server started on %s://%s:%d...' % \
1976 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00001978 websocket_options.use_basic_auth = self.options.ws_basic_auth
Adam Rice9476b8c2018-08-02 15:28:43 +00001979 elif self.options.server_type == SERVER_PROXY:
1980 ProxyRequestHandler.redirect_connect_to_localhost = \
1981 self.options.redirect_connect_to_localhost
1982 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
1983 print 'Proxy server started on port %d...' % server.server_port
1984 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001985 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00001986 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001987 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00001988 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001989 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001990 server_data['port'] = server.server_port
1991 elif self.options.server_type == SERVER_FTP:
1992 my_data_dir = self.__make_data_dir()
1993
1994 # Instantiate a dummy authorizer for managing 'virtual' users
1995 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1996
xleng9d4c45f2015-05-04 16:26:12 -07001997 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00001998 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1999
xleng9d4c45f2015-05-04 16:26:12 -07002000 # Define a read-only anonymous user unless disabled
2001 if not self.options.no_anonymous_ftp_user:
2002 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002003
2004 # Instantiate FTP handler class
2005 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2006 ftp_handler.authorizer = authorizer
2007
2008 # Define a customized banner (string returned when client connects)
2009 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2010 pyftpdlib.ftpserver.__ver__)
2011
2012 # Instantiate FTP server class and listen to address:port
2013 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2014 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002015 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002016 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002017 raise testserver_base.OptionError('unknown server type' +
2018 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002019
mattm@chromium.org830a3712012-11-07 23:00:07 +00002020 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002021
mattm@chromium.org830a3712012-11-07 23:00:07 +00002022 def run_server(self):
2023 if self.__ocsp_server:
2024 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002025
mattm@chromium.org830a3712012-11-07 23:00:07 +00002026 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002027
mattm@chromium.org830a3712012-11-07 23:00:07 +00002028 if self.__ocsp_server:
2029 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002030
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 def add_options(self):
2032 testserver_base.TestServerRunner.add_options(self)
2033 self.option_parser.add_option('-f', '--ftp', action='store_const',
2034 const=SERVER_FTP, default=SERVER_HTTP,
2035 dest='server_type',
2036 help='start up an FTP server.')
Adam Rice9476b8c2018-08-02 15:28:43 +00002037 self.option_parser.add_option('--proxy', action='store_const',
2038 const=SERVER_PROXY,
2039 default=SERVER_HTTP, dest='server_type',
2040 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002041 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2042 const=SERVER_BASIC_AUTH_PROXY,
2043 default=SERVER_HTTP, dest='server_type',
2044 help='start up a proxy server which requires '
2045 'basic authentication.')
2046 self.option_parser.add_option('--websocket', action='store_const',
2047 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2048 dest='server_type',
2049 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 self.option_parser.add_option('--https', action='store_true',
2051 dest='https', help='Specify that https '
2052 'should be used.')
2053 self.option_parser.add_option('--cert-and-key-file',
2054 dest='cert_and_key_file', help='specify the '
2055 'path to the file containing the certificate '
2056 'and private key for the server in PEM '
2057 'format')
mattm10ede842016-11-29 11:57:16 -08002058 self.option_parser.add_option('--aia-intermediate', action='store_true',
2059 dest='aia_intermediate',
2060 help='generate a certificate chain that '
2061 'requires AIA cert fetching, and run a '
2062 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2064 help='The type of OCSP response generated '
2065 'for the automatically generated '
2066 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002067 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2068 default='valid', help='The validity of the '
2069 'range between thisUpdate and nextUpdate')
2070 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2071 default='valid', help='producedAt relative '
2072 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002073 self.option_parser.add_option('--ocsp-intermediate',
2074 dest='ocsp_intermediate', default=None,
2075 help='If specified, the automatically '
2076 'generated chain will include an '
2077 'intermediate certificate with this type '
2078 'of OCSP response (see docs for --ocsp)')
2079 self.option_parser.add_option('--ocsp-intermediate-date',
2080 dest='ocsp_intermediate_date',
2081 default='valid', help='The validity of the '
2082 'range between thisUpdate and nextUpdate')
2083 self.option_parser.add_option('--ocsp-intermediate-produced',
2084 dest='ocsp_intermediate_produced',
2085 default='valid', help='producedAt relative '
2086 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002087 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2088 default=0, type=int,
2089 help='If non-zero then the generated '
2090 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002091 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2092 default="127.0.0.1",
2093 help='The generated certificate will have '
2094 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2096 default='0', type='int',
2097 help='If nonzero, certain TLS connections '
2098 'will be aborted in order to test version '
2099 'fallback. 1 means all TLS versions will be '
2100 'aborted. 2 means TLS 1.1 or higher will be '
2101 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002102 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002103 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002104 self.option_parser.add_option('--tls-intolerance-type',
2105 dest='tls_intolerance_type',
2106 default="alert",
2107 help='Controls how the server reacts to a '
2108 'TLS version it is intolerant to. Valid '
2109 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002110 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2111 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002112 default='',
2113 help='Base64 encoded SCT list. If set, '
2114 'server will respond with a '
2115 'signed_certificate_timestamp TLS extension '
2116 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002117 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2118 default=False, const=True,
2119 action='store_const',
2120 help='If given, TLS_FALLBACK_SCSV support '
2121 'will be enabled. This causes the server to '
2122 'reject fallback connections from compatible '
2123 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002124 self.option_parser.add_option('--staple-ocsp-response',
2125 dest='staple_ocsp_response',
2126 default=False, action='store_true',
2127 help='If set, server will staple the OCSP '
2128 'response whenever OCSP is on and the client '
2129 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130 self.option_parser.add_option('--https-record-resume',
2131 dest='record_resume', const=True,
2132 default=False, action='store_const',
2133 help='Record resumption cache events rather '
2134 'than resuming as normal. Allows the use of '
2135 'the /ssl-session-cache request')
2136 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2137 help='Require SSL client auth on every '
2138 'connection.')
2139 self.option_parser.add_option('--ssl-client-ca', action='append',
2140 default=[], help='Specify that the client '
2141 'certificate request should include the CA '
2142 'named in the subject of the DER-encoded '
2143 'certificate contained in the specified '
2144 'file. This option may appear multiple '
2145 'times, indicating multiple CA names should '
2146 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002147 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2148 default=[], help='Specify that the client '
2149 'certificate request should include the '
2150 'specified certificate_type value. This '
2151 'option may appear multiple times, '
2152 'indicating multiple values should be send '
2153 'in the request. Valid values are '
2154 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2155 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002156 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2157 help='Specify the bulk encryption '
2158 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002159 'SSL server. Valid values are "aes128gcm", '
2160 '"aes256", "aes128", "3des", "rc4". If '
2161 'omitted, all algorithms will be used. This '
2162 'option may appear multiple times, '
2163 'indicating multiple algorithms should be '
2164 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002165 self.option_parser.add_option('--ssl-key-exchange', action='append',
2166 help='Specify the key exchange algorithm(s)'
2167 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002168 'Valid values are "rsa", "dhe_rsa", '
2169 '"ecdhe_rsa". If omitted, all algorithms '
2170 'will be used. This option may appear '
2171 'multiple times, indicating multiple '
2172 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002173 self.option_parser.add_option('--alpn-protocols', action='append',
2174 help='Specify the list of ALPN protocols. '
2175 'The server will not send an ALPN response '
2176 'if this list does not overlap with the '
2177 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002178 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002179 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002180 'an NPN response. The server will not'
2181 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002182 self.option_parser.add_option('--file-root-url', default='/files/',
2183 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002184 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2185 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2186 dest='ws_basic_auth',
2187 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002188 self.option_parser.add_option('--ocsp-server-unavailable',
2189 dest='ocsp_server_unavailable',
2190 default=False, action='store_true',
2191 help='If set, the OCSP server will return '
2192 'a tryLater status rather than the actual '
2193 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002194 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2195 type='int', dest='ocsp_proxy_port_number',
2196 help='Port allocated for OCSP proxy '
2197 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002198 self.option_parser.add_option('--alert-after-handshake',
2199 dest='alert_after_handshake',
2200 default=False, action='store_true',
2201 help='If set, the server will send a fatal '
2202 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002203 self.option_parser.add_option('--no-anonymous-ftp-user',
2204 dest='no_anonymous_ftp_user',
2205 default=False, action='store_true',
2206 help='If set, the FTP server will not create '
2207 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002208 self.option_parser.add_option('--disable-channel-id', action='store_true')
2209 self.option_parser.add_option('--disable-extended-master-secret',
2210 action='store_true')
David Benjaminf839f1c2018-10-16 06:01:29 +00002211 self.option_parser.add_option('--simulate-tls13-downgrade',
2212 action='store_true')
2213 self.option_parser.add_option('--simulate-tls12-downgrade',
2214 action='store_true')
2215 self.option_parser.add_option('--tls-max-version', default='0', type='int',
2216 help='If non-zero, the maximum TLS version '
2217 'to support. 1 means TLS 1.0, 2 means '
2218 'TLS 1.1, and 3 means TLS 1.2.')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002219 self.option_parser.add_option('--redirect-connect-to-localhost',
2220 dest='redirect_connect_to_localhost',
2221 default=False, action='store_true',
2222 help='If set, the Proxy server will connect '
2223 'to localhost instead of the requested URL '
2224 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002225
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002226
initial.commit94958cf2008-07-26 22:42:52 +00002227if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002228 sys.exit(ServerRunner().main())