blob: 55404855b5d9a137d0d525dedf79d2f08ac98567 [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
58import echo_message
59import testserver_base
60
maruel@chromium.org756cf982009-03-05 12:46:38 +000061SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000062SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000063SERVER_TCP_ECHO = 2
64SERVER_UDP_ECHO = 3
65SERVER_BASIC_AUTH_PROXY = 4
66SERVER_WEBSOCKET = 5
Adam Rice9476b8c2018-08-02 15:28:43 +000067SERVER_PROXY = 6
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000068
69# Default request queue size for WebSocketServer.
70_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000071
dadrian4ccf51c2016-07-20 15:36:58 -070072OCSP_STATES_NO_SINGLE_RESPONSE = {
73 minica.OCSP_STATE_INVALID_RESPONSE,
74 minica.OCSP_STATE_UNAUTHORIZED,
75 minica.OCSP_STATE_TRY_LATER,
76 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
77}
78
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079class WebSocketOptions:
80 """Holds options for WebSocketServer."""
81
82 def __init__(self, host, port, data_dir):
83 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
84 self.server_host = host
85 self.port = port
86 self.websock_handlers = data_dir
87 self.scan_dir = None
88 self.allow_handlers_outside_root_dir = False
89 self.websock_handlers_map_file = None
90 self.cgi_directories = []
91 self.is_executable_method = None
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000092
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000093 self.use_tls = False
94 self.private_key = None
95 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000096 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000097 self.tls_client_ca = None
98 self.use_basic_auth = False
Keita Suzuki83e26f92020-03-06 09:42:48 +000099 self.basic_auth_credential = 'Basic ' + base64.b64encode(
100 'test:test').decode()
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000101
mattm@chromium.org830a3712012-11-07 23:00:07 +0000102
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000103class RecordingSSLSessionCache(object):
104 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
105 lookups and inserts in order to test session cache behaviours."""
106
107 def __init__(self):
108 self.log = []
109
110 def __getitem__(self, sessionID):
111 self.log.append(('lookup', sessionID))
112 raise KeyError()
113
114 def __setitem__(self, sessionID, session):
115 self.log.append(('insert', sessionID))
116
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000117
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000118class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
119 testserver_base.BrokenPipeHandlerMixIn,
120 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000121 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000122 verification."""
123
124 pass
125
Adam Rice34b2e312018-04-06 16:48:30 +0000126class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
127 HTTPServer):
128 """This variant of HTTPServer creates a new thread for every connection. It
129 should only be used with handlers that are known to be threadsafe."""
130
131 pass
132
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000133class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
134 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000135 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000136 """This is a specialization of HTTPServer that serves an
137 OCSP response"""
138
139 def serve_forever_on_thread(self):
140 self.thread = threading.Thread(target = self.serve_forever,
141 name = "OCSPServerThread")
142 self.thread.start()
143
144 def stop_serving(self):
145 self.shutdown()
146 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000147
mattm@chromium.org830a3712012-11-07 23:00:07 +0000148
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000149class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000150 testserver_base.ClientRestrictingServerMixIn,
151 testserver_base.BrokenPipeHandlerMixIn,
152 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000153 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000154 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000155
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000156 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000157 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700158 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
159 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000160 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700161 fallback_scsv_enabled, ocsp_response,
David Benjaminf839f1c2018-10-16 06:01:29 +0000162 alert_after_handshake, disable_channel_id, disable_ems,
163 simulate_tls13_downgrade, simulate_tls12_downgrade,
164 tls_max_version):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000165 self.cert_chain = tlslite.api.X509CertChain()
166 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000167 # Force using only python implementation - otherwise behavior is different
168 # depending on whether m2crypto Python module is present (error is thrown
169 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
170 # the hood.
171 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
172 private=True,
173 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000174 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000175 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000176 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700177 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000178 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000179 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000180 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000181
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000182 if ssl_client_auth:
183 for ca_file in ssl_client_cas:
184 s = open(ca_file).read()
185 x509 = tlslite.api.X509()
186 x509.parse(s)
187 self.ssl_client_cas.append(x509.subject)
188
189 for cert_type in ssl_client_cert_types:
190 self.ssl_client_cert_types.append({
191 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800196 # Enable SSLv3 for testing purposes.
197 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000202 if tls_intolerant != 0:
203 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
204 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700205 if alert_after_handshake:
206 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700207 if disable_channel_id:
208 self.ssl_handshake_settings.enableChannelID = False
209 if disable_ems:
210 self.ssl_handshake_settings.enableExtendedMasterSecret = False
David Benjaminf839f1c2018-10-16 06:01:29 +0000211 if simulate_tls13_downgrade:
212 self.ssl_handshake_settings.simulateTLS13Downgrade = True
213 if simulate_tls12_downgrade:
214 self.ssl_handshake_settings.simulateTLS12Downgrade = True
215 if tls_max_version != 0:
216 self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
bnc5fb33bd2016-08-05 12:09:21 -0700217 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000218
rsleevi8146efa2015-03-16 12:31:24 -0700219 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000220 # If record_resume_info is true then we'll replace the session cache with
221 # an object that records the lookups and inserts that it sees.
222 self.session_cache = RecordingSSLSessionCache()
223 else:
224 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000225 testserver_base.StoppableHTTPServer.__init__(self,
226 server_address,
227 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000228
229 def handshake(self, tlsConnection):
230 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000231
initial.commit94958cf2008-07-26 22:42:52 +0000232 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000233 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000234 tlsConnection.handshakeServer(certChain=self.cert_chain,
235 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000236 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000237 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000238 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000239 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000240 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700241 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000242 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000243 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000244 fallbackSCSV=self.fallback_scsv_enabled,
245 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000246 tlsConnection.ignoreAbruptClose = True
247 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000248 except tlslite.api.TLSAbruptCloseError:
249 # Ignore abrupt close.
250 return True
initial.commit94958cf2008-07-26 22:42:52 +0000251 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000252 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000253 return False
254
akalin@chromium.org154bb132010-11-12 02:20:27 +0000255
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000256class FTPServer(testserver_base.ClientRestrictingServerMixIn,
257 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000258 """This is a specialization of FTPServer that adds client verification."""
259
260 pass
261
262
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000263class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
264 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000265 """A TCP echo server that echoes back what it has received."""
266
267 def server_bind(self):
268 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000269
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000270 SocketServer.TCPServer.server_bind(self)
271 host, port = self.socket.getsockname()[:2]
272 self.server_name = socket.getfqdn(host)
273 self.server_port = port
274
275 def serve_forever(self):
276 self.stop = False
277 self.nonce_time = None
278 while not self.stop:
279 self.handle_request()
280 self.socket.close()
281
282
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000283class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
284 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000285 """A UDP echo server that echoes back what it has received."""
286
287 def server_bind(self):
288 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000289
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000290 SocketServer.UDPServer.server_bind(self)
291 host, port = self.socket.getsockname()[:2]
292 self.server_name = socket.getfqdn(host)
293 self.server_port = port
294
295 def serve_forever(self):
296 self.stop = False
297 self.nonce_time = None
298 while not self.stop:
299 self.handle_request()
300 self.socket.close()
301
302
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000303class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000304 # Class variables to allow for persistence state between page handler
305 # invocations
306 rst_limits = {}
307 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000308
309 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000310 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000311 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000312 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000313 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000314 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000315 self.NoCacheMaxAgeTimeHandler,
316 self.NoCacheTimeHandler,
317 self.CacheTimeHandler,
318 self.CacheExpiresHandler,
319 self.CacheProxyRevalidateHandler,
320 self.CachePrivateHandler,
321 self.CachePublicHandler,
322 self.CacheSMaxAgeHandler,
323 self.CacheMustRevalidateHandler,
324 self.CacheMustRevalidateMaxAgeHandler,
325 self.CacheNoStoreHandler,
326 self.CacheNoStoreMaxAgeHandler,
327 self.CacheNoTransformHandler,
328 self.DownloadHandler,
329 self.DownloadFinishHandler,
330 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000331 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000332 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000333 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000334 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000335 self.SetCookieHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000336 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000337 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000338 self.AuthBasicHandler,
339 self.AuthDigestHandler,
340 self.SlowServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000341 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700343 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000344 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000345 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000346 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000347 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700348 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700349 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000350 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000351 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000353 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000354 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000355 self.PostOnlyFileHandler,
356 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000357 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000358 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000359 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000360 head_handlers = [
361 self.FileHandler,
362 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000363
maruel@google.come250a9b2009-03-10 17:39:46 +0000364 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000365 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000366 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000367 'gif': 'image/gif',
368 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000369 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700370 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000371 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000372 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000373 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000374 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000375 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000376 }
initial.commit94958cf2008-07-26 22:42:52 +0000377 self._default_mime_type = 'text/html'
378
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000379 testserver_base.BasePageHandler.__init__(self, request, client_address,
380 socket_server, connect_handlers,
381 get_handlers, head_handlers,
382 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000383
initial.commit94958cf2008-07-26 22:42:52 +0000384 def GetMIMETypeFromName(self, file_name):
385 """Returns the mime type for the specified file_name. So far it only looks
386 at the file extension."""
387
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000388 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000389 if len(extension) == 0:
390 # no extension.
391 return self._default_mime_type
392
ericroman@google.comc17ca532009-05-07 03:51:05 +0000393 # extension starts with a dot, so we need to remove it
394 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000395
initial.commit94958cf2008-07-26 22:42:52 +0000396 def NoCacheMaxAgeTimeHandler(self):
397 """This request handler yields a page with the title set to the current
398 system time, and no caching requested."""
399
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000400 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000401 return False
402
403 self.send_response(200)
404 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000405 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000406 self.end_headers()
407
maruel@google.come250a9b2009-03-10 17:39:46 +0000408 self.wfile.write('<html><head><title>%s</title></head></html>' %
409 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000410
411 return True
412
413 def NoCacheTimeHandler(self):
414 """This request handler yields a page with the title set to the current
415 system time, and no caching requested."""
416
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000417 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000418 return False
419
420 self.send_response(200)
421 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000422 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000423 self.end_headers()
424
maruel@google.come250a9b2009-03-10 17:39:46 +0000425 self.wfile.write('<html><head><title>%s</title></head></html>' %
426 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000427
428 return True
429
430 def CacheTimeHandler(self):
431 """This request handler yields a page with the title set to the current
432 system time, and allows caching for one minute."""
433
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000434 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000435 return False
436
437 self.send_response(200)
438 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000439 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.end_headers()
441
maruel@google.come250a9b2009-03-10 17:39:46 +0000442 self.wfile.write('<html><head><title>%s</title></head></html>' %
443 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000444
445 return True
446
447 def CacheExpiresHandler(self):
448 """This request handler yields a page with the title set to the current
449 system time, and set the page to expire on 1 Jan 2099."""
450
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000451 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000452 return False
453
454 self.send_response(200)
455 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000456 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000457 self.end_headers()
458
maruel@google.come250a9b2009-03-10 17:39:46 +0000459 self.wfile.write('<html><head><title>%s</title></head></html>' %
460 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000461
462 return True
463
464 def CacheProxyRevalidateHandler(self):
465 """This request handler yields a page with the title set to the current
466 system time, and allows caching for 60 seconds"""
467
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000468 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000469 return False
470
471 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000472 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000473 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
474 self.end_headers()
475
maruel@google.come250a9b2009-03-10 17:39:46 +0000476 self.wfile.write('<html><head><title>%s</title></head></html>' %
477 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000478
479 return True
480
481 def CachePrivateHandler(self):
482 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700483 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000484
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000485 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000486 return False
487
488 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000489 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000490 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000491 self.end_headers()
492
maruel@google.come250a9b2009-03-10 17:39:46 +0000493 self.wfile.write('<html><head><title>%s</title></head></html>' %
494 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000495
496 return True
497
498 def CachePublicHandler(self):
499 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700500 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000501
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000502 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000503 return False
504
505 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000506 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000507 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000508 self.end_headers()
509
maruel@google.come250a9b2009-03-10 17:39:46 +0000510 self.wfile.write('<html><head><title>%s</title></head></html>' %
511 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000512
513 return True
514
515 def CacheSMaxAgeHandler(self):
516 """This request handler yields a page with the title set to the current
517 system time, and does not allow for caching."""
518
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000519 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000520 return False
521
522 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000523 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000524 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
525 self.end_headers()
526
maruel@google.come250a9b2009-03-10 17:39:46 +0000527 self.wfile.write('<html><head><title>%s</title></head></html>' %
528 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000529
530 return True
531
532 def CacheMustRevalidateHandler(self):
533 """This request handler yields a page with the title set to the current
534 system time, and does not allow caching."""
535
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000536 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000537 return False
538
539 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000540 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000541 self.send_header('Cache-Control', 'must-revalidate')
542 self.end_headers()
543
maruel@google.come250a9b2009-03-10 17:39:46 +0000544 self.wfile.write('<html><head><title>%s</title></head></html>' %
545 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000546
547 return True
548
549 def CacheMustRevalidateMaxAgeHandler(self):
550 """This request handler yields a page with the title set to the current
551 system time, and does not allow caching event though max-age of 60
552 seconds is specified."""
553
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000554 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000555 return False
556
557 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000558 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000559 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
560 self.end_headers()
561
maruel@google.come250a9b2009-03-10 17:39:46 +0000562 self.wfile.write('<html><head><title>%s</title></head></html>' %
563 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000564
565 return True
566
initial.commit94958cf2008-07-26 22:42:52 +0000567 def CacheNoStoreHandler(self):
568 """This request handler yields a page with the title set to the current
569 system time, and does not allow the page to be stored."""
570
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000571 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000572 return False
573
574 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000575 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000576 self.send_header('Cache-Control', 'no-store')
577 self.end_headers()
578
maruel@google.come250a9b2009-03-10 17:39:46 +0000579 self.wfile.write('<html><head><title>%s</title></head></html>' %
580 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000581
582 return True
583
584 def CacheNoStoreMaxAgeHandler(self):
585 """This request handler yields a page with the title set to the current
586 system time, and does not allow the page to be stored even though max-age
587 of 60 seconds is specified."""
588
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000589 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000590 return False
591
592 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000593 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000594 self.send_header('Cache-Control', 'max-age=60, no-store')
595 self.end_headers()
596
maruel@google.come250a9b2009-03-10 17:39:46 +0000597 self.wfile.write('<html><head><title>%s</title></head></html>' %
598 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000599
600 return True
601
602
603 def CacheNoTransformHandler(self):
604 """This request handler yields a page with the title set to the current
605 system time, and does not allow the content to transformed during
606 user-agent caching"""
607
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000608 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000609 return False
610
611 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000612 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000613 self.send_header('Cache-Control', 'no-transform')
614 self.end_headers()
615
maruel@google.come250a9b2009-03-10 17:39:46 +0000616 self.wfile.write('<html><head><title>%s</title></head></html>' %
617 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000618
619 return True
620
621 def EchoHeader(self):
622 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000623
ananta@chromium.org219b2062009-10-23 16:09:41 +0000624 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000625
ananta@chromium.org56812d02011-04-07 17:52:05 +0000626 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000627 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000628 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000629
ananta@chromium.org56812d02011-04-07 17:52:05 +0000630 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000631
632 def EchoHeaderHelper(self, echo_header):
633 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000634
ananta@chromium.org219b2062009-10-23 16:09:41 +0000635 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000636 return False
637
638 query_char = self.path.find('?')
639 if query_char != -1:
640 header_name = self.path[query_char+1:]
641
642 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000643 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000644 if echo_header == '/echoheadercache':
645 self.send_header('Cache-control', 'max-age=60000')
646 else:
647 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000648 # insert a vary header to properly indicate that the cachability of this
649 # request is subject to value of the request header being echoed.
650 if len(header_name) > 0:
651 self.send_header('Vary', header_name)
652 self.end_headers()
653
654 if len(header_name) > 0:
655 self.wfile.write(self.headers.getheader(header_name))
656
657 return True
658
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000659 def ReadRequestBody(self):
660 """This function reads the body of the current HTTP request, handling
661 both plain and chunked transfer encoded requests."""
662
663 if self.headers.getheader('transfer-encoding') != 'chunked':
664 length = int(self.headers.getheader('content-length'))
665 return self.rfile.read(length)
666
667 # Read the request body as chunks.
668 body = ""
669 while True:
670 line = self.rfile.readline()
671 length = int(line, 16)
672 if length == 0:
673 self.rfile.readline()
674 break
675 body += self.rfile.read(length)
676 self.rfile.read(2)
677 return body
678
initial.commit94958cf2008-07-26 22:42:52 +0000679 def EchoHandler(self):
680 """This handler just echoes back the payload of the request, for testing
681 form submission."""
682
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000683 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000684 return False
685
hirono2838c572015-01-21 12:18:11 -0800686 _, _, _, _, query, _ = urlparse.urlparse(self.path)
687 query_params = cgi.parse_qs(query, True)
688 if 'status' in query_params:
689 self.send_response(int(query_params['status'][0]))
690 else:
691 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000692 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000693 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000694 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000695 return True
696
697 def EchoTitleHandler(self):
698 """This handler is like Echo, but sets the page title to the request."""
699
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000700 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000701 return False
702
703 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000704 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000705 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000706 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000707 self.wfile.write('<html><head><title>')
708 self.wfile.write(request)
709 self.wfile.write('</title></head></html>')
710 return True
711
712 def EchoAllHandler(self):
713 """This handler yields a (more) human-readable page listing information
714 about the request header & contents."""
715
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000716 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000717 return False
718
719 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000720 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000721 self.end_headers()
722 self.wfile.write('<html><head><style>'
723 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
724 '</style></head><body>'
725 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000726 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000727 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000728
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000729 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000730 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000731 params = cgi.parse_qs(qs, keep_blank_values=1)
732
733 for param in params:
734 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000735
736 self.wfile.write('</pre>')
737
738 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
739
740 self.wfile.write('</body></html>')
741 return True
742
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000743 def EchoMultipartPostHandler(self):
744 """This handler echoes received multipart post data as json format."""
745
746 if not (self._ShouldHandleRequest("/echomultipartpost") or
747 self._ShouldHandleRequest("/searchbyimage")):
748 return False
749
750 content_type, parameters = cgi.parse_header(
751 self.headers.getheader('content-type'))
752 if content_type == 'multipart/form-data':
753 post_multipart = cgi.parse_multipart(self.rfile, parameters)
754 elif content_type == 'application/x-www-form-urlencoded':
755 raise Exception('POST by application/x-www-form-urlencoded is '
756 'not implemented.')
757 else:
758 post_multipart = {}
759
760 # Since the data can be binary, we encode them by base64.
761 post_multipart_base64_encoded = {}
762 for field, values in post_multipart.items():
763 post_multipart_base64_encoded[field] = [base64.b64encode(value)
764 for value in values]
765
766 result = {'POST_multipart' : post_multipart_base64_encoded}
767
768 self.send_response(200)
769 self.send_header("Content-type", "text/plain")
770 self.end_headers()
771 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
772 return True
773
initial.commit94958cf2008-07-26 22:42:52 +0000774 def DownloadHandler(self):
775 """This handler sends a downloadable file with or without reporting
776 the size (6K)."""
777
778 if self.path.startswith("/download-unknown-size"):
779 send_length = False
780 elif self.path.startswith("/download-known-size"):
781 send_length = True
782 else:
783 return False
784
785 #
786 # The test which uses this functionality is attempting to send
787 # small chunks of data to the client. Use a fairly large buffer
788 # so that we'll fill chrome's IO buffer enough to force it to
789 # actually write the data.
790 # See also the comments in the client-side of this test in
791 # download_uitest.cc
792 #
793 size_chunk1 = 35*1024
794 size_chunk2 = 10*1024
795
796 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000797 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000798 self.send_header('Cache-Control', 'max-age=0')
799 if send_length:
800 self.send_header('Content-Length', size_chunk1 + size_chunk2)
801 self.end_headers()
802
803 # First chunk of data:
804 self.wfile.write("*" * size_chunk1)
805 self.wfile.flush()
806
807 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000808 self.server.wait_for_download = True
809 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.server.handle_request()
811
812 # Second chunk of data:
813 self.wfile.write("*" * size_chunk2)
814 return True
815
816 def DownloadFinishHandler(self):
817 """This handler just tells the server to finish the current download."""
818
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000819 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000820 return False
821
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000822 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000823 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000824 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000825 self.send_header('Cache-Control', 'max-age=0')
826 self.end_headers()
827 return True
828
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000829 def _ReplaceFileData(self, data, query_parameters):
830 """Replaces matching substrings in a file.
831
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000832 If the 'replace_text' URL query parameter is present, it is expected to be
833 of the form old_text:new_text, which indicates that any old_text strings in
834 the file are replaced with new_text. Multiple 'replace_text' parameters may
835 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000836
837 If the parameters are not present, |data| is returned.
838 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000839
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000840 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000841 replace_text_values = query_dict.get('replace_text', [])
842 for replace_text_value in replace_text_values:
843 replace_text_args = replace_text_value.split(':')
844 if len(replace_text_args) != 2:
845 raise ValueError(
846 'replace_text must be of form old_text:new_text. Actual value: %s' %
847 replace_text_value)
848 old_text_b64, new_text_b64 = replace_text_args
849 old_text = base64.urlsafe_b64decode(old_text_b64)
850 new_text = base64.urlsafe_b64decode(new_text_b64)
851 data = data.replace(old_text, new_text)
852 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000853
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000854 def ZipFileHandler(self):
855 """This handler sends the contents of the requested file in compressed form.
856 Can pass in a parameter that specifies that the content length be
857 C - the compressed size (OK),
858 U - the uncompressed size (Non-standard, but handled),
859 S - less than compressed (OK because we keep going),
860 M - larger than compressed but less than uncompressed (an error),
861 L - larger than uncompressed (an error)
862 Example: compressedfiles/Picture_1.doc?C
863 """
864
865 prefix = "/compressedfiles/"
866 if not self.path.startswith(prefix):
867 return False
868
869 # Consume a request body if present.
870 if self.command == 'POST' or self.command == 'PUT' :
871 self.ReadRequestBody()
872
873 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
874
875 if not query in ('C', 'U', 'S', 'M', 'L'):
876 return False
877
878 sub_path = url_path[len(prefix):]
879 entries = sub_path.split('/')
880 file_path = os.path.join(self.server.data_dir, *entries)
881 if os.path.isdir(file_path):
882 file_path = os.path.join(file_path, 'index.html')
883
884 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000885 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000886 self.send_error(404)
887 return True
888
889 f = open(file_path, "rb")
890 data = f.read()
891 uncompressed_len = len(data)
892 f.close()
893
894 # Compress the data.
895 data = zlib.compress(data)
896 compressed_len = len(data)
897
898 content_length = compressed_len
899 if query == 'U':
900 content_length = uncompressed_len
901 elif query == 'S':
902 content_length = compressed_len / 2
903 elif query == 'M':
904 content_length = (compressed_len + uncompressed_len) / 2
905 elif query == 'L':
906 content_length = compressed_len + uncompressed_len
907
908 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000909 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000910 self.send_header('Content-encoding', 'deflate')
911 self.send_header('Connection', 'close')
912 self.send_header('Content-Length', content_length)
913 self.send_header('ETag', '\'' + file_path + '\'')
914 self.end_headers()
915
916 self.wfile.write(data)
917
918 return True
919
initial.commit94958cf2008-07-26 22:42:52 +0000920 def FileHandler(self):
921 """This handler sends the contents of the requested file. Wow, it's like
922 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000923
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000924 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000925 if not self.path.startswith(prefix):
926 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000927 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000928
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000929 def PostOnlyFileHandler(self):
930 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000931
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000932 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000933 if not self.path.startswith(prefix):
934 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000935 return self._FileHandlerHelper(prefix)
936
937 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000938 request_body = ''
939 if self.command == 'POST' or self.command == 'PUT':
940 # Consume a request body if present.
941 request_body = self.ReadRequestBody()
942
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000943 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000944 query_dict = cgi.parse_qs(query)
945
946 expected_body = query_dict.get('expected_body', [])
947 if expected_body and request_body not in expected_body:
948 self.send_response(404)
949 self.end_headers()
950 self.wfile.write('')
951 return True
952
953 expected_headers = query_dict.get('expected_headers', [])
954 for expected_header in expected_headers:
955 header_name, expected_value = expected_header.split(':')
956 if self.headers.getheader(header_name) != expected_value:
957 self.send_response(404)
958 self.end_headers()
959 self.wfile.write('')
960 return True
961
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000962 sub_path = url_path[len(prefix):]
963 entries = sub_path.split('/')
964 file_path = os.path.join(self.server.data_dir, *entries)
965 if os.path.isdir(file_path):
966 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000967
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000968 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000969 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000970 self.send_error(404)
971 return True
972
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000973 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000974 data = f.read()
975 f.close()
976
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000977 data = self._ReplaceFileData(data, query)
978
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000979 old_protocol_version = self.protocol_version
980
initial.commit94958cf2008-07-26 22:42:52 +0000981 # If file.mock-http-headers exists, it contains the headers we
982 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000983 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000984 if os.path.isfile(headers_path):
985 f = open(headers_path, "r")
986
987 # "HTTP/1.1 200 OK"
988 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000989 http_major, http_minor, status_code = re.findall(
990 'HTTP/(\d+).(\d+) (\d+)', response)[0]
991 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000992 self.send_response(int(status_code))
993
994 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000995 header_values = re.findall('(\S+):\s*(.*)', line)
996 if len(header_values) > 0:
997 # "name: value"
998 name, value = header_values[0]
999 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001000 f.close()
1001 else:
1002 # Could be more generic once we support mime-type sniffing, but for
1003 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001004
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001005 range_header = self.headers.get('Range')
1006 if range_header and range_header.startswith('bytes='):
1007 # Note this doesn't handle all valid byte range_header values (i.e.
1008 # left open ended ones), just enough for what we needed so far.
1009 range_header = range_header[6:].split('-')
1010 start = int(range_header[0])
1011 if range_header[1]:
1012 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001013 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001014 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001015
1016 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001017 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1018 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001019 self.send_header('Content-Range', content_range)
1020 data = data[start: end + 1]
1021 else:
1022 self.send_response(200)
1023
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001024 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001025 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001026 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001027 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001028 self.end_headers()
1029
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001030 if (self.command != 'HEAD'):
1031 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001032
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001033 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001034 return True
1035
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001036 def SetCookieHandler(self):
1037 """This handler just sets a cookie, for testing cookie handling."""
1038
1039 if not self._ShouldHandleRequest("/set-cookie"):
1040 return False
1041
1042 query_char = self.path.find('?')
1043 if query_char != -1:
1044 cookie_values = self.path[query_char + 1:].split('&')
1045 else:
1046 cookie_values = ("",)
1047 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001048 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001049 for cookie_value in cookie_values:
1050 self.send_header('Set-Cookie', '%s' % cookie_value)
1051 self.end_headers()
1052 for cookie_value in cookie_values:
1053 self.wfile.write('%s' % cookie_value)
1054 return True
1055
mattm@chromium.org983fc462012-06-30 00:52:08 +00001056 def ExpectAndSetCookieHandler(self):
1057 """Expects some cookies to be sent, and if they are, sets more cookies.
1058
1059 The expect parameter specifies a required cookie. May be specified multiple
1060 times.
1061 The set parameter specifies a cookie to set if all required cookies are
1062 preset. May be specified multiple times.
1063 The data parameter specifies the response body data to be returned."""
1064
1065 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1066 return False
1067
1068 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1069 query_dict = cgi.parse_qs(query)
1070 cookies = set()
1071 if 'Cookie' in self.headers:
1072 cookie_header = self.headers.getheader('Cookie')
1073 cookies.update([s.strip() for s in cookie_header.split(';')])
1074 got_all_expected_cookies = True
1075 for expected_cookie in query_dict.get('expect', []):
1076 if expected_cookie not in cookies:
1077 got_all_expected_cookies = False
1078 self.send_response(200)
1079 self.send_header('Content-Type', 'text/html')
1080 if got_all_expected_cookies:
1081 for cookie_value in query_dict.get('set', []):
1082 self.send_header('Set-Cookie', '%s' % cookie_value)
1083 self.end_headers()
1084 for data_value in query_dict.get('data', []):
1085 self.wfile.write(data_value)
1086 return True
1087
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001088 def SetHeaderHandler(self):
1089 """This handler sets a response header. Parameters are in the
1090 key%3A%20value&key2%3A%20value2 format."""
1091
1092 if not self._ShouldHandleRequest("/set-header"):
1093 return False
1094
1095 query_char = self.path.find('?')
1096 if query_char != -1:
1097 headers_values = self.path[query_char + 1:].split('&')
1098 else:
1099 headers_values = ("",)
1100 self.send_response(200)
1101 self.send_header('Content-Type', 'text/html')
1102 for header_value in headers_values:
1103 header_value = urllib.unquote(header_value)
1104 (key, value) = header_value.split(': ', 1)
1105 self.send_header(key, value)
1106 self.end_headers()
1107 for header_value in headers_values:
1108 self.wfile.write('%s' % header_value)
1109 return True
1110
initial.commit94958cf2008-07-26 22:42:52 +00001111 def AuthBasicHandler(self):
1112 """This handler tests 'Basic' authentication. It just sends a page with
1113 title 'user/pass' if you succeed."""
1114
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001115 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001116 return False
1117
1118 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001119 expected_password = 'secret'
1120 realm = 'testrealm'
1121 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001122
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001123 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1124 query_params = cgi.parse_qs(query, True)
1125 if 'set-cookie-if-challenged' in query_params:
1126 set_cookie_if_challenged = True
1127 if 'password' in query_params:
1128 expected_password = query_params['password'][0]
1129 if 'realm' in query_params:
1130 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001131
initial.commit94958cf2008-07-26 22:42:52 +00001132 auth = self.headers.getheader('authorization')
1133 try:
1134 if not auth:
1135 raise Exception('no auth')
1136 b64str = re.findall(r'Basic (\S+)', auth)[0]
1137 userpass = base64.b64decode(b64str)
1138 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001139 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001140 raise Exception('wrong password')
1141 except Exception, e:
1142 # Authentication failed.
1143 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001144 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001145 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001146 if set_cookie_if_challenged:
1147 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001148 self.end_headers()
1149 self.wfile.write('<html><head>')
1150 self.wfile.write('<title>Denied: %s</title>' % e)
1151 self.wfile.write('</head><body>')
1152 self.wfile.write('auth=%s<p>' % auth)
1153 self.wfile.write('b64str=%s<p>' % b64str)
1154 self.wfile.write('username: %s<p>' % username)
1155 self.wfile.write('userpass: %s<p>' % userpass)
1156 self.wfile.write('password: %s<p>' % password)
1157 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1158 self.wfile.write('</body></html>')
1159 return True
1160
1161 # Authentication successful. (Return a cachable response to allow for
1162 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001163 old_protocol_version = self.protocol_version
1164 self.protocol_version = "HTTP/1.1"
1165
initial.commit94958cf2008-07-26 22:42:52 +00001166 if_none_match = self.headers.getheader('if-none-match')
1167 if if_none_match == "abc":
1168 self.send_response(304)
1169 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001170 elif url_path.endswith(".gif"):
1171 # Using chrome/test/data/google/logo.gif as the test image
1172 test_image_path = ['google', 'logo.gif']
1173 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1174 if not os.path.isfile(gif_path):
1175 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001176 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001177 return True
1178
1179 f = open(gif_path, "rb")
1180 data = f.read()
1181 f.close()
1182
1183 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001184 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001185 self.send_header('Cache-control', 'max-age=60000')
1186 self.send_header('Etag', 'abc')
1187 self.end_headers()
1188 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001189 else:
1190 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001191 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001192 self.send_header('Cache-control', 'max-age=60000')
1193 self.send_header('Etag', 'abc')
1194 self.end_headers()
1195 self.wfile.write('<html><head>')
1196 self.wfile.write('<title>%s/%s</title>' % (username, password))
1197 self.wfile.write('</head><body>')
1198 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001199 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001200 self.wfile.write('</body></html>')
1201
rvargas@google.com54453b72011-05-19 01:11:11 +00001202 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001203 return True
1204
tonyg@chromium.org75054202010-03-31 22:06:10 +00001205 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001206 """Returns a nonce that's stable per request path for the server's lifetime.
1207 This is a fake implementation. A real implementation would only use a given
1208 nonce a single time (hence the name n-once). However, for the purposes of
1209 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001210
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001211 Args:
1212 force_reset: Iff set, the nonce will be changed. Useful for testing the
1213 "stale" response.
1214 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001215
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001216 if force_reset or not self.server.nonce_time:
1217 self.server.nonce_time = time.time()
1218 return hashlib.md5('privatekey%s%d' %
1219 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001220
1221 def AuthDigestHandler(self):
1222 """This handler tests 'Digest' authentication.
1223
1224 It just sends a page with title 'user/pass' if you succeed.
1225
1226 A stale response is sent iff "stale" is present in the request path.
1227 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001228
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001229 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001230 return False
1231
tonyg@chromium.org75054202010-03-31 22:06:10 +00001232 stale = 'stale' in self.path
1233 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001234 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001235 password = 'secret'
1236 realm = 'testrealm'
1237
1238 auth = self.headers.getheader('authorization')
1239 pairs = {}
1240 try:
1241 if not auth:
1242 raise Exception('no auth')
1243 if not auth.startswith('Digest'):
1244 raise Exception('not digest')
1245 # Pull out all the name="value" pairs as a dictionary.
1246 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1247
1248 # Make sure it's all valid.
1249 if pairs['nonce'] != nonce:
1250 raise Exception('wrong nonce')
1251 if pairs['opaque'] != opaque:
1252 raise Exception('wrong opaque')
1253
1254 # Check the 'response' value and make sure it matches our magic hash.
1255 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001256 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001257 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001258 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001259 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001260 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001261 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1262 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001263 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001264
1265 if pairs['response'] != response:
1266 raise Exception('wrong password')
1267 except Exception, e:
1268 # Authentication failed.
1269 self.send_response(401)
1270 hdr = ('Digest '
1271 'realm="%s", '
1272 'domain="/", '
1273 'qop="auth", '
1274 'algorithm=MD5, '
1275 'nonce="%s", '
1276 'opaque="%s"') % (realm, nonce, opaque)
1277 if stale:
1278 hdr += ', stale="TRUE"'
1279 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001280 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001281 self.end_headers()
1282 self.wfile.write('<html><head>')
1283 self.wfile.write('<title>Denied: %s</title>' % e)
1284 self.wfile.write('</head><body>')
1285 self.wfile.write('auth=%s<p>' % auth)
1286 self.wfile.write('pairs=%s<p>' % pairs)
1287 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1288 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1289 self.wfile.write('</body></html>')
1290 return True
1291
1292 # Authentication successful.
1293 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001294 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001295 self.end_headers()
1296 self.wfile.write('<html><head>')
1297 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1298 self.wfile.write('</head><body>')
1299 self.wfile.write('auth=%s<p>' % auth)
1300 self.wfile.write('pairs=%s<p>' % pairs)
1301 self.wfile.write('</body></html>')
1302
1303 return True
1304
1305 def SlowServerHandler(self):
1306 """Wait for the user suggested time before responding. The syntax is
1307 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001308
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001309 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001310 return False
1311 query_char = self.path.find('?')
1312 wait_sec = 1.0
1313 if query_char >= 0:
1314 try:
davidben05f82202015-03-31 13:48:07 -07001315 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001316 except ValueError:
1317 pass
1318 time.sleep(wait_sec)
1319 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001320 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001321 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001322 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001323 return True
1324
creis@google.com2f4f6a42011-03-25 19:44:19 +00001325 def NoContentHandler(self):
1326 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001327
creis@google.com2f4f6a42011-03-25 19:44:19 +00001328 if not self._ShouldHandleRequest("/nocontent"):
1329 return False
1330 self.send_response(204)
1331 self.end_headers()
1332 return True
1333
initial.commit94958cf2008-07-26 22:42:52 +00001334 def ServerRedirectHandler(self):
1335 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001336 '/server-redirect?http://foo.bar/asdf' to redirect to
1337 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001338
1339 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001340 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001341 return False
1342
1343 query_char = self.path.find('?')
1344 if query_char < 0 or len(self.path) <= query_char + 1:
1345 self.sendRedirectHelp(test_name)
1346 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001347 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001348
1349 self.send_response(301) # moved permanently
1350 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001351 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001352 self.end_headers()
1353 self.wfile.write('<html><head>')
1354 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1355
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001356 return True
initial.commit94958cf2008-07-26 22:42:52 +00001357
naskoe7a0d0d2014-09-29 08:53:05 -07001358 def CrossSiteRedirectHandler(self):
1359 """Sends a server redirect to the given site. The syntax is
1360 '/cross-site/hostname/...' to redirect to //hostname/...
1361 It is used to navigate between different Sites, causing
1362 cross-site/cross-process navigations in the browser."""
1363
1364 test_name = "/cross-site"
1365 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001366 return False
1367
1368 params = urllib.unquote(self.path[(len(test_name) + 1):])
1369 slash = params.find('/')
1370 if slash < 0:
1371 self.sendRedirectHelp(test_name)
1372 return True
1373
1374 host = params[:slash]
1375 path = params[(slash+1):]
1376 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1377
1378 self.send_response(301) # moved permanently
1379 self.send_header('Location', dest)
1380 self.send_header('Content-Type', 'text/html')
1381 self.end_headers()
1382 self.wfile.write('<html><head>')
1383 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1384
1385 return True
1386
initial.commit94958cf2008-07-26 22:42:52 +00001387 def ClientRedirectHandler(self):
1388 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001389 '/client-redirect?http://foo.bar/asdf' to redirect to
1390 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001391
1392 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001393 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001394 return False
1395
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001396 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001397 if query_char < 0 or len(self.path) <= query_char + 1:
1398 self.sendRedirectHelp(test_name)
1399 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001400 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001401
1402 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001403 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001404 self.end_headers()
1405 self.wfile.write('<html><head>')
1406 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1407 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1408
1409 return True
1410
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001411 def GetSSLSessionCacheHandler(self):
1412 """Send a reply containing a log of the session cache operations."""
1413
1414 if not self._ShouldHandleRequest('/ssl-session-cache'):
1415 return False
1416
1417 self.send_response(200)
1418 self.send_header('Content-Type', 'text/plain')
1419 self.end_headers()
1420 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001421 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001422 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001423 self.wfile.write('Pass --https-record-resume in order to use' +
1424 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001425 return True
1426
1427 for (action, sessionID) in log:
1428 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001429 return True
1430
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001431 def SSLManySmallRecords(self):
1432 """Sends a reply consisting of a variety of small writes. These will be
1433 translated into a series of small SSL records when used over an HTTPS
1434 server."""
1435
1436 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1437 return False
1438
1439 self.send_response(200)
1440 self.send_header('Content-Type', 'text/plain')
1441 self.end_headers()
1442
1443 # Write ~26K of data, in 1350 byte chunks
1444 for i in xrange(20):
1445 self.wfile.write('*' * 1350)
1446 self.wfile.flush()
1447 return True
1448
agl@chromium.org04700be2013-03-02 18:40:41 +00001449 def GetChannelID(self):
1450 """Send a reply containing the hashed ChannelID that the client provided."""
1451
1452 if not self._ShouldHandleRequest('/channel-id'):
1453 return False
1454
1455 self.send_response(200)
1456 self.send_header('Content-Type', 'text/plain')
1457 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001458 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001459 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1460 return True
1461
pneubeckfd4f0442015-08-07 04:55:10 -07001462 def GetClientCert(self):
1463 """Send a reply whether a client certificate was provided."""
1464
1465 if not self._ShouldHandleRequest('/client-cert'):
1466 return False
1467
1468 self.send_response(200)
1469 self.send_header('Content-Type', 'text/plain')
1470 self.end_headers()
1471
1472 cert_chain = self.server.tlsConnection.session.clientCertChain
1473 if cert_chain != None:
1474 self.wfile.write('got client cert with fingerprint: ' +
1475 cert_chain.getFingerprint())
1476 else:
1477 self.wfile.write('got no client cert')
1478 return True
1479
davidben599e7e72014-09-03 16:19:09 -07001480 def ClientCipherListHandler(self):
1481 """Send a reply containing the cipher suite list that the client
1482 provided. Each cipher suite value is serialized in decimal, followed by a
1483 newline."""
1484
1485 if not self._ShouldHandleRequest('/client-cipher-list'):
1486 return False
1487
1488 self.send_response(200)
1489 self.send_header('Content-Type', 'text/plain')
1490 self.end_headers()
1491
davidben11682512014-10-06 21:09:11 -07001492 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1493 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001494 return True
1495
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001496 def CloseSocketHandler(self):
1497 """Closes the socket without sending anything."""
1498
1499 if not self._ShouldHandleRequest('/close-socket'):
1500 return False
1501
1502 self.wfile.close()
1503 return True
1504
initial.commit94958cf2008-07-26 22:42:52 +00001505 def DefaultResponseHandler(self):
1506 """This is the catch-all response handler for requests that aren't handled
1507 by one of the special handlers above.
1508 Note that we specify the content-length as without it the https connection
1509 is not closed properly (and the browser keeps expecting data)."""
1510
1511 contents = "Default response given for path: " + self.path
1512 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001513 self.send_header('Content-Type', 'text/html')
1514 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001515 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001516 if (self.command != 'HEAD'):
1517 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001518 return True
1519
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001520 def RedirectConnectHandler(self):
1521 """Sends a redirect to the CONNECT request for www.redirect.com. This
1522 response is not specified by the RFC, so the browser should not follow
1523 the redirect."""
1524
1525 if (self.path.find("www.redirect.com") < 0):
1526 return False
1527
1528 dest = "http://www.destination.com/foo.js"
1529
1530 self.send_response(302) # moved temporarily
1531 self.send_header('Location', dest)
1532 self.send_header('Connection', 'close')
1533 self.end_headers()
1534 return True
1535
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001536 def ServerAuthConnectHandler(self):
1537 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1538 response doesn't make sense because the proxy server cannot request
1539 server authentication."""
1540
1541 if (self.path.find("www.server-auth.com") < 0):
1542 return False
1543
1544 challenge = 'Basic realm="WallyWorld"'
1545
1546 self.send_response(401) # unauthorized
1547 self.send_header('WWW-Authenticate', challenge)
1548 self.send_header('Connection', 'close')
1549 self.end_headers()
1550 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001551
1552 def DefaultConnectResponseHandler(self):
1553 """This is the catch-all response handler for CONNECT requests that aren't
1554 handled by one of the special handlers above. Real Web servers respond
1555 with 400 to CONNECT requests."""
1556
1557 contents = "Your client has issued a malformed or illegal request."
1558 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001559 self.send_header('Content-Type', 'text/html')
1560 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001561 self.end_headers()
1562 self.wfile.write(contents)
1563 return True
1564
initial.commit94958cf2008-07-26 22:42:52 +00001565 # called by the redirect handling function when there is no parameter
1566 def sendRedirectHelp(self, redirect_name):
1567 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001568 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001569 self.end_headers()
1570 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1571 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1572 self.wfile.write('</body></html>')
1573
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001574 # called by chunked handling function
1575 def sendChunkHelp(self, chunk):
1576 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1577 self.wfile.write('%X\r\n' % len(chunk))
1578 self.wfile.write(chunk)
1579 self.wfile.write('\r\n')
1580
akalin@chromium.org154bb132010-11-12 02:20:27 +00001581
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001582class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001583 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001584 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001585 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001586 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001587 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001588 testserver_base.BasePageHandler.__init__(self, request, client_address,
1589 socket_server, [], handlers, [],
1590 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001591
1592 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001593 if self._ShouldHandleRequest("/ocsp"):
1594 response = self.ocsp_response
1595 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1596 response = self.ocsp_response_intermediate
1597 else:
mattm10ede842016-11-29 11:57:16 -08001598 return False
1599 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001600 self.send_response(200)
1601 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001602 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001603 self.end_headers()
1604
Matt Mueller55aef642018-05-02 18:53:57 +00001605 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001606
mattm10ede842016-11-29 11:57:16 -08001607 def CaIssuersResponse(self):
1608 if not self._ShouldHandleRequest("/ca_issuers"):
1609 return False
1610 print 'handling ca_issuers request'
1611 self.send_response(200)
1612 self.send_header('Content-Type', 'application/pkix-cert')
1613 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1614 self.end_headers()
1615
1616 self.wfile.write(self.ca_issuers_response)
1617
mattm@chromium.org830a3712012-11-07 23:00:07 +00001618
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001619class TCPEchoHandler(SocketServer.BaseRequestHandler):
1620 """The RequestHandler class for TCP echo server.
1621
1622 It is instantiated once per connection to the server, and overrides the
1623 handle() method to implement communication to the client.
1624 """
1625
1626 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001627 """Handles the request from the client and constructs a response."""
1628
1629 data = self.request.recv(65536).strip()
1630 # Verify the "echo request" message received from the client. Send back
1631 # "echo response" message if "echo request" message is valid.
1632 try:
1633 return_data = echo_message.GetEchoResponseData(data)
1634 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001635 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001636 except ValueError:
1637 return
1638
1639 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001640
1641
1642class UDPEchoHandler(SocketServer.BaseRequestHandler):
1643 """The RequestHandler class for UDP echo server.
1644
1645 It is instantiated once per connection to the server, and overrides the
1646 handle() method to implement communication to the client.
1647 """
1648
1649 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001650 """Handles the request from the client and constructs a response."""
1651
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001652 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001653 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001654 # Verify the "echo request" message received from the client. Send back
1655 # "echo response" message if "echo request" message is valid.
1656 try:
1657 return_data = echo_message.GetEchoResponseData(data)
1658 if not return_data:
1659 return
1660 except ValueError:
1661 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001662 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001663
1664
Adam Rice9476b8c2018-08-02 15:28:43 +00001665class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1666 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1667 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001668 """
1669
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001670 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001671
bashi@chromium.org33233532012-09-08 17:37:24 +00001672 def _start_read_write(self, sock):
1673 sock.setblocking(0)
1674 self.request.setblocking(0)
1675 rlist = [self.request, sock]
1676 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001677 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001678 if errors:
1679 self.send_response(500)
1680 self.end_headers()
1681 return
1682 for s in ready_sockets:
1683 received = s.recv(1024)
1684 if len(received) == 0:
1685 return
1686 if s == self.request:
1687 other = sock
1688 else:
1689 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001690 # This will lose data if the kernel write buffer fills up.
1691 # TODO(ricea): Correctly use the return value to track how much was
1692 # written and buffer the rest. Use select to determine when the socket
1693 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001694 other.send(received)
1695
1696 def _do_common_method(self):
1697 url = urlparse.urlparse(self.path)
1698 port = url.port
1699 if not port:
1700 if url.scheme == 'http':
1701 port = 80
1702 elif url.scheme == 'https':
1703 port = 443
1704 if not url.hostname or not port:
1705 self.send_response(400)
1706 self.end_headers()
1707 return
1708
1709 if len(url.path) == 0:
1710 path = '/'
1711 else:
1712 path = url.path
1713 if len(url.query) > 0:
1714 path = '%s?%s' % (url.path, url.query)
1715
1716 sock = None
1717 try:
1718 sock = socket.create_connection((url.hostname, port))
1719 sock.send('%s %s %s\r\n' % (
1720 self.command, path, self.protocol_version))
1721 for header in self.headers.headers:
1722 header = header.strip()
1723 if (header.lower().startswith('connection') or
1724 header.lower().startswith('proxy')):
1725 continue
1726 sock.send('%s\r\n' % header)
1727 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001728 # This is wrong: it will pass through connection-level headers and
1729 # misbehave on connection reuse. The only reason it works at all is that
1730 # our test servers have never supported connection reuse.
1731 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001732 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001733 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001734 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001735 self.send_response(500)
1736 self.end_headers()
1737 finally:
1738 if sock is not None:
1739 sock.close()
1740
1741 def do_CONNECT(self):
1742 try:
1743 pos = self.path.rfind(':')
1744 host = self.path[:pos]
1745 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001746 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001747 self.send_response(400)
1748 self.end_headers()
1749
Adam Rice9476b8c2018-08-02 15:28:43 +00001750 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001751 host = "127.0.0.1"
1752
Adam Rice54443aa2018-06-06 00:11:54 +00001753 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001754 try:
1755 sock = socket.create_connection((host, port))
1756 self.send_response(200, 'Connection established')
1757 self.end_headers()
1758 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001759 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001760 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001761 self.send_response(500)
1762 self.end_headers()
1763 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001764 if sock is not None:
1765 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001766
1767 def do_GET(self):
1768 self._do_common_method()
1769
1770 def do_HEAD(self):
1771 self._do_common_method()
1772
Adam Rice9476b8c2018-08-02 15:28:43 +00001773class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1774 """A request handler that behaves as a proxy server which requires
1775 basic authentication.
1776 """
1777
1778 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1779
1780 def parse_request(self):
1781 """Overrides parse_request to check credential."""
1782
1783 if not ProxyRequestHandler.parse_request(self):
1784 return False
1785
1786 auth = self.headers.getheader('Proxy-Authorization')
1787 if auth != self._AUTH_CREDENTIAL:
1788 self.send_response(407)
1789 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1790 self.end_headers()
1791 return False
1792
1793 return True
1794
bashi@chromium.org33233532012-09-08 17:37:24 +00001795
mattm@chromium.org830a3712012-11-07 23:00:07 +00001796class ServerRunner(testserver_base.TestServerRunner):
1797 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001798
mattm@chromium.org830a3712012-11-07 23:00:07 +00001799 def __init__(self):
1800 super(ServerRunner, self).__init__()
1801 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001802
mattm@chromium.org830a3712012-11-07 23:00:07 +00001803 def __make_data_dir(self):
1804 if self.options.data_dir:
1805 if not os.path.isdir(self.options.data_dir):
1806 raise testserver_base.OptionError('specified data dir not found: ' +
1807 self.options.data_dir + ' exiting...')
1808 my_data_dir = self.options.data_dir
1809 else:
1810 # Create the default path to our data dir, relative to the exe dir.
Asanka Herath0ec37152019-08-02 15:23:57 +00001811 my_data_dir = os.path.join(BASE_DIR, "..", "..", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001812
mattm@chromium.org830a3712012-11-07 23:00:07 +00001813 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001814
Matt Mueller55aef642018-05-02 18:53:57 +00001815 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1816 if states_option is None:
1817 return None, None, None
1818
1819 ocsp_states = list()
1820 for ocsp_state_arg in states_option.split(':'):
1821 if ocsp_state_arg == 'ok':
1822 ocsp_state = minica.OCSP_STATE_GOOD
1823 elif ocsp_state_arg == 'revoked':
1824 ocsp_state = minica.OCSP_STATE_REVOKED
1825 elif ocsp_state_arg == 'invalid':
1826 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1827 elif ocsp_state_arg == 'unauthorized':
1828 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1829 elif ocsp_state_arg == 'unknown':
1830 ocsp_state = minica.OCSP_STATE_UNKNOWN
1831 elif ocsp_state_arg == 'later':
1832 ocsp_state = minica.OCSP_STATE_TRY_LATER
1833 elif ocsp_state_arg == 'invalid_data':
1834 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1835 elif ocsp_state_arg == "mismatched_serial":
1836 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1837 else:
1838 raise testserver_base.OptionError('unknown OCSP status: ' +
1839 ocsp_state_arg)
1840 ocsp_states.append(ocsp_state)
1841
1842 if len(ocsp_states) > 1:
1843 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1844 raise testserver_base.OptionError('Multiple OCSP responses '
1845 'incompatible with states ' + str(ocsp_states))
1846
1847 ocsp_dates = list()
1848 for ocsp_date_arg in date_option.split(':'):
1849 if ocsp_date_arg == 'valid':
1850 ocsp_date = minica.OCSP_DATE_VALID
1851 elif ocsp_date_arg == 'old':
1852 ocsp_date = minica.OCSP_DATE_OLD
1853 elif ocsp_date_arg == 'early':
1854 ocsp_date = minica.OCSP_DATE_EARLY
1855 elif ocsp_date_arg == 'long':
1856 ocsp_date = minica.OCSP_DATE_LONG
1857 elif ocsp_date_arg == 'longer':
1858 ocsp_date = minica.OCSP_DATE_LONGER
1859 else:
1860 raise testserver_base.OptionError('unknown OCSP date: ' +
1861 ocsp_date_arg)
1862 ocsp_dates.append(ocsp_date)
1863
1864 if len(ocsp_states) != len(ocsp_dates):
1865 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1866 'count')
1867
1868 ocsp_produced = None
1869 if produced_option == 'valid':
1870 ocsp_produced = minica.OCSP_PRODUCED_VALID
1871 elif produced_option == 'before':
1872 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1873 elif produced_option == 'after':
1874 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1875 else:
1876 raise testserver_base.OptionError('unknown OCSP produced: ' +
1877 produced_option)
1878
1879 return ocsp_states, ocsp_dates, ocsp_produced
1880
mattm@chromium.org830a3712012-11-07 23:00:07 +00001881 def create_server(self, server_data):
1882 port = self.options.port
1883 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001884
Adam Rice54443aa2018-06-06 00:11:54 +00001885 logging.basicConfig()
1886
estark21667d62015-04-08 21:00:16 -07001887 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1888 # will result in a call to |getaddrinfo|, which fails with "nodename
1889 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001890 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001891 if self.options.server_type == SERVER_WEBSOCKET and \
1892 host == "localhost" and \
1893 port == 0:
1894 host = "127.0.0.1"
1895
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001896 # Construct the subjectAltNames for any ad-hoc generated certificates.
1897 # As host can be either a DNS name or IP address, attempt to determine
1898 # which it is, so it can be placed in the appropriate SAN.
1899 dns_sans = None
1900 ip_sans = None
1901 ip = None
1902 try:
1903 ip = socket.inet_aton(host)
1904 ip_sans = [ip]
1905 except socket.error:
1906 pass
1907 if ip is None:
1908 dns_sans = [host]
1909
mattm@chromium.org830a3712012-11-07 23:00:07 +00001910 if self.options.server_type == SERVER_HTTP:
1911 if self.options.https:
1912 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001913 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001914 if self.options.cert_and_key_file:
1915 if not os.path.isfile(self.options.cert_and_key_file):
1916 raise testserver_base.OptionError(
1917 'specified server cert file not found: ' +
1918 self.options.cert_and_key_file + ' exiting...')
1919 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001920 elif self.options.aia_intermediate:
1921 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1922 print ('AIA server started on %s:%d...' %
1923 (host, self.__ocsp_server.server_port))
1924
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001925 ocsp_server_port = self.__ocsp_server.server_port
1926 if self.options.ocsp_proxy_port_number != 0:
1927 ocsp_server_port = self.options.ocsp_proxy_port_number
1928 server_data['ocsp_port'] = self.__ocsp_server.server_port
1929
mattm10ede842016-11-29 11:57:16 -08001930 (pem_cert_and_key, intermediate_cert_der) = \
1931 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001932 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001933 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001934 ca_issuers_url =
1935 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08001936 serial = self.options.cert_serial)
1937
1938 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00001939 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08001940 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001941 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 # generate a new certificate and run an OCSP server for it.
1943 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001944 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001946
Matt Mueller55aef642018-05-02 18:53:57 +00001947 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
1948 self.options.ocsp,
1949 self.options.ocsp_date,
1950 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001951
Matt Mueller55aef642018-05-02 18:53:57 +00001952 (ocsp_intermediate_states, ocsp_intermediate_dates,
1953 ocsp_intermediate_produced) = self.__parse_ocsp_options(
1954 self.options.ocsp_intermediate,
1955 self.options.ocsp_intermediate_date,
1956 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001957
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001958 ocsp_server_port = self.__ocsp_server.server_port
1959 if self.options.ocsp_proxy_port_number != 0:
1960 ocsp_server_port = self.options.ocsp_proxy_port_number
1961 server_data['ocsp_port'] = self.__ocsp_server.server_port
1962
Matt Mueller55aef642018-05-02 18:53:57 +00001963 pem_cert_and_key, (ocsp_der,
1964 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00001965 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001966 ip_sans = ip_sans,
1967 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001968 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07001969 ocsp_states = ocsp_states,
1970 ocsp_dates = ocsp_dates,
1971 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00001972 ocsp_intermediate_url = (
1973 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
1974 if ocsp_intermediate_states else None),
1975 ocsp_intermediate_states = ocsp_intermediate_states,
1976 ocsp_intermediate_dates = ocsp_intermediate_dates,
1977 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00001978 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001979
davidben3e2564a2014-11-07 18:51:00 -08001980 if self.options.ocsp_server_unavailable:
1981 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00001982 self.__ocsp_server.ocsp_response_intermediate = \
1983 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08001984 else:
1985 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00001986 self.__ocsp_server.ocsp_response_intermediate = \
1987 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08001988 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001989
1990 for ca_cert in self.options.ssl_client_ca:
1991 if not os.path.isfile(ca_cert):
1992 raise testserver_base.OptionError(
1993 'specified trusted client CA file not found: ' + ca_cert +
1994 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001995
1996 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08001997 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00001998 # TODO(mattm): Staple the intermediate response too (if applicable,
1999 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08002000 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002001
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2003 self.options.ssl_client_auth,
2004 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002005 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002006 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002007 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002008 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002009 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002010 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002011 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002012 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002013 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002014 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002015 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002016 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002017 self.options.alert_after_handshake,
2018 self.options.disable_channel_id,
David Benjaminf839f1c2018-10-16 06:01:29 +00002019 self.options.disable_extended_master_secret,
2020 self.options.simulate_tls13_downgrade,
2021 self.options.simulate_tls12_downgrade,
2022 self.options.tls_max_version)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002023 print 'HTTPS server started on https://%s:%d...' % \
2024 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002025 else:
2026 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002027 print 'HTTP server started on http://%s:%d...' % \
2028 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002029
2030 server.data_dir = self.__make_data_dir()
2031 server.file_root_url = self.options.file_root_url
2032 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002033 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2035 # is required to work correctly. It should be fixed from pywebsocket side.
2036 os.chdir(self.__make_data_dir())
2037 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002038 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002039 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002040 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002041 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002042 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2043 if not os.path.isfile(key_path):
2044 raise testserver_base.OptionError(
2045 'specified server cert file not found: ' +
2046 self.options.cert_and_key_file + ' exiting...')
2047 websocket_options.private_key = key_path
2048 websocket_options.certificate = key_path
2049
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002051 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 websocket_options.tls_client_auth = True
2053 if len(self.options.ssl_client_ca) != 1:
2054 raise testserver_base.OptionError(
2055 'one trusted client CA file should be specified')
2056 if not os.path.isfile(self.options.ssl_client_ca[0]):
2057 raise testserver_base.OptionError(
2058 'specified trusted client CA file not found: ' +
2059 self.options.ssl_client_ca[0] + ' exiting...')
2060 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002061 print 'Trying to start websocket server on %s://%s:%d...' % \
2062 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002064 print 'WebSocket server started on %s://%s:%d...' % \
2065 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002066 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002067 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 elif self.options.server_type == SERVER_TCP_ECHO:
2069 # Used for generating the key (randomly) that encodes the "echo request"
2070 # message.
2071 random.seed()
2072 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002073 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002074 server_data['port'] = server.server_port
2075 elif self.options.server_type == SERVER_UDP_ECHO:
2076 # Used for generating the key (randomly) that encodes the "echo request"
2077 # message.
2078 random.seed()
2079 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002080 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002081 server_data['port'] = server.server_port
Adam Rice9476b8c2018-08-02 15:28:43 +00002082 elif self.options.server_type == SERVER_PROXY:
2083 ProxyRequestHandler.redirect_connect_to_localhost = \
2084 self.options.redirect_connect_to_localhost
2085 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
2086 print 'Proxy server started on port %d...' % server.server_port
2087 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00002089 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002090 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00002091 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002092 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002093 server_data['port'] = server.server_port
2094 elif self.options.server_type == SERVER_FTP:
2095 my_data_dir = self.__make_data_dir()
2096
2097 # Instantiate a dummy authorizer for managing 'virtual' users
2098 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2099
xleng9d4c45f2015-05-04 16:26:12 -07002100 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002101 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2102
xleng9d4c45f2015-05-04 16:26:12 -07002103 # Define a read-only anonymous user unless disabled
2104 if not self.options.no_anonymous_ftp_user:
2105 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002106
2107 # Instantiate FTP handler class
2108 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2109 ftp_handler.authorizer = authorizer
2110
2111 # Define a customized banner (string returned when client connects)
2112 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2113 pyftpdlib.ftpserver.__ver__)
2114
2115 # Instantiate FTP server class and listen to address:port
2116 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2117 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002118 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002119 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002120 raise testserver_base.OptionError('unknown server type' +
2121 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002122
mattm@chromium.org830a3712012-11-07 23:00:07 +00002123 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002124
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 def run_server(self):
2126 if self.__ocsp_server:
2127 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002128
mattm@chromium.org830a3712012-11-07 23:00:07 +00002129 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002130
mattm@chromium.org830a3712012-11-07 23:00:07 +00002131 if self.__ocsp_server:
2132 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002133
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 def add_options(self):
2135 testserver_base.TestServerRunner.add_options(self)
2136 self.option_parser.add_option('-f', '--ftp', action='store_const',
2137 const=SERVER_FTP, default=SERVER_HTTP,
2138 dest='server_type',
2139 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002140 self.option_parser.add_option('--tcp-echo', action='store_const',
2141 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2142 dest='server_type',
2143 help='start up a tcp echo server.')
2144 self.option_parser.add_option('--udp-echo', action='store_const',
2145 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2146 dest='server_type',
2147 help='start up a udp echo server.')
Adam Rice9476b8c2018-08-02 15:28:43 +00002148 self.option_parser.add_option('--proxy', action='store_const',
2149 const=SERVER_PROXY,
2150 default=SERVER_HTTP, dest='server_type',
2151 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002152 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2153 const=SERVER_BASIC_AUTH_PROXY,
2154 default=SERVER_HTTP, dest='server_type',
2155 help='start up a proxy server which requires '
2156 'basic authentication.')
2157 self.option_parser.add_option('--websocket', action='store_const',
2158 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2159 dest='server_type',
2160 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002161 self.option_parser.add_option('--https', action='store_true',
2162 dest='https', help='Specify that https '
2163 'should be used.')
2164 self.option_parser.add_option('--cert-and-key-file',
2165 dest='cert_and_key_file', help='specify the '
2166 'path to the file containing the certificate '
2167 'and private key for the server in PEM '
2168 'format')
mattm10ede842016-11-29 11:57:16 -08002169 self.option_parser.add_option('--aia-intermediate', action='store_true',
2170 dest='aia_intermediate',
2171 help='generate a certificate chain that '
2172 'requires AIA cert fetching, and run a '
2173 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002174 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2175 help='The type of OCSP response generated '
2176 'for the automatically generated '
2177 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002178 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2179 default='valid', help='The validity of the '
2180 'range between thisUpdate and nextUpdate')
2181 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2182 default='valid', help='producedAt relative '
2183 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002184 self.option_parser.add_option('--ocsp-intermediate',
2185 dest='ocsp_intermediate', default=None,
2186 help='If specified, the automatically '
2187 'generated chain will include an '
2188 'intermediate certificate with this type '
2189 'of OCSP response (see docs for --ocsp)')
2190 self.option_parser.add_option('--ocsp-intermediate-date',
2191 dest='ocsp_intermediate_date',
2192 default='valid', help='The validity of the '
2193 'range between thisUpdate and nextUpdate')
2194 self.option_parser.add_option('--ocsp-intermediate-produced',
2195 dest='ocsp_intermediate_produced',
2196 default='valid', help='producedAt relative '
2197 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002198 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2199 default=0, type=int,
2200 help='If non-zero then the generated '
2201 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002202 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2203 default="127.0.0.1",
2204 help='The generated certificate will have '
2205 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002206 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2207 default='0', type='int',
2208 help='If nonzero, certain TLS connections '
2209 'will be aborted in order to test version '
2210 'fallback. 1 means all TLS versions will be '
2211 'aborted. 2 means TLS 1.1 or higher will be '
2212 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002213 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002214 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002215 self.option_parser.add_option('--tls-intolerance-type',
2216 dest='tls_intolerance_type',
2217 default="alert",
2218 help='Controls how the server reacts to a '
2219 'TLS version it is intolerant to. Valid '
2220 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002221 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2222 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002223 default='',
2224 help='Base64 encoded SCT list. If set, '
2225 'server will respond with a '
2226 'signed_certificate_timestamp TLS extension '
2227 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002228 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2229 default=False, const=True,
2230 action='store_const',
2231 help='If given, TLS_FALLBACK_SCSV support '
2232 'will be enabled. This causes the server to '
2233 'reject fallback connections from compatible '
2234 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002235 self.option_parser.add_option('--staple-ocsp-response',
2236 dest='staple_ocsp_response',
2237 default=False, action='store_true',
2238 help='If set, server will staple the OCSP '
2239 'response whenever OCSP is on and the client '
2240 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002241 self.option_parser.add_option('--https-record-resume',
2242 dest='record_resume', const=True,
2243 default=False, action='store_const',
2244 help='Record resumption cache events rather '
2245 'than resuming as normal. Allows the use of '
2246 'the /ssl-session-cache request')
2247 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2248 help='Require SSL client auth on every '
2249 'connection.')
2250 self.option_parser.add_option('--ssl-client-ca', action='append',
2251 default=[], help='Specify that the client '
2252 'certificate request should include the CA '
2253 'named in the subject of the DER-encoded '
2254 'certificate contained in the specified '
2255 'file. This option may appear multiple '
2256 'times, indicating multiple CA names should '
2257 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002258 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2259 default=[], help='Specify that the client '
2260 'certificate request should include the '
2261 'specified certificate_type value. This '
2262 'option may appear multiple times, '
2263 'indicating multiple values should be send '
2264 'in the request. Valid values are '
2265 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2266 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002267 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2268 help='Specify the bulk encryption '
2269 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002270 'SSL server. Valid values are "aes128gcm", '
2271 '"aes256", "aes128", "3des", "rc4". If '
2272 'omitted, all algorithms will be used. This '
2273 'option may appear multiple times, '
2274 'indicating multiple algorithms should be '
2275 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002276 self.option_parser.add_option('--ssl-key-exchange', action='append',
2277 help='Specify the key exchange algorithm(s)'
2278 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002279 'Valid values are "rsa", "dhe_rsa", '
2280 '"ecdhe_rsa". If omitted, all algorithms '
2281 'will be used. This option may appear '
2282 'multiple times, indicating multiple '
2283 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002284 self.option_parser.add_option('--alpn-protocols', action='append',
2285 help='Specify the list of ALPN protocols. '
2286 'The server will not send an ALPN response '
2287 'if this list does not overlap with the '
2288 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002289 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002290 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002291 'an NPN response. The server will not'
2292 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002293 self.option_parser.add_option('--file-root-url', default='/files/',
2294 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002295 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2296 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2297 dest='ws_basic_auth',
2298 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002299 self.option_parser.add_option('--ocsp-server-unavailable',
2300 dest='ocsp_server_unavailable',
2301 default=False, action='store_true',
2302 help='If set, the OCSP server will return '
2303 'a tryLater status rather than the actual '
2304 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002305 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2306 type='int', dest='ocsp_proxy_port_number',
2307 help='Port allocated for OCSP proxy '
2308 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002309 self.option_parser.add_option('--alert-after-handshake',
2310 dest='alert_after_handshake',
2311 default=False, action='store_true',
2312 help='If set, the server will send a fatal '
2313 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002314 self.option_parser.add_option('--no-anonymous-ftp-user',
2315 dest='no_anonymous_ftp_user',
2316 default=False, action='store_true',
2317 help='If set, the FTP server will not create '
2318 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002319 self.option_parser.add_option('--disable-channel-id', action='store_true')
2320 self.option_parser.add_option('--disable-extended-master-secret',
2321 action='store_true')
David Benjaminf839f1c2018-10-16 06:01:29 +00002322 self.option_parser.add_option('--simulate-tls13-downgrade',
2323 action='store_true')
2324 self.option_parser.add_option('--simulate-tls12-downgrade',
2325 action='store_true')
2326 self.option_parser.add_option('--tls-max-version', default='0', type='int',
2327 help='If non-zero, the maximum TLS version '
2328 'to support. 1 means TLS 1.0, 2 means '
2329 'TLS 1.1, and 3 means TLS 1.2.')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002330 self.option_parser.add_option('--redirect-connect-to-localhost',
2331 dest='redirect_connect_to_localhost',
2332 default=False, action='store_true',
2333 help='If set, the Proxy server will connect '
2334 'to localhost instead of the requested URL '
2335 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002336
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002337
initial.commit94958cf2008-07-26 22:42:52 +00002338if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002339 sys.exit(ServerRunner().main())