blob: ea1d9cd3908dd54168d7044ddd44f531dcd50380 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
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).
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', '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
92 self.allow_draft75 = False
93 self.strict = True
94
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000095 self.use_tls = False
96 self.private_key = None
97 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000098 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000099 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +0000100 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000101 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000102 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000103
mattm@chromium.org830a3712012-11-07 23:00:07 +0000104
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000105class RecordingSSLSessionCache(object):
106 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
107 lookups and inserts in order to test session cache behaviours."""
108
109 def __init__(self):
110 self.log = []
111
112 def __getitem__(self, sessionID):
113 self.log.append(('lookup', sessionID))
114 raise KeyError()
115
116 def __setitem__(self, sessionID, session):
117 self.log.append(('insert', sessionID))
118
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000119
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000120class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
121 testserver_base.BrokenPipeHandlerMixIn,
122 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000123 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000124 verification."""
125
126 pass
127
Adam Rice34b2e312018-04-06 16:48:30 +0000128class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
129 HTTPServer):
130 """This variant of HTTPServer creates a new thread for every connection. It
131 should only be used with handlers that are known to be threadsafe."""
132
133 pass
134
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000135class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
136 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000137 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000138 """This is a specialization of HTTPServer that serves an
139 OCSP response"""
140
141 def serve_forever_on_thread(self):
142 self.thread = threading.Thread(target = self.serve_forever,
143 name = "OCSPServerThread")
144 self.thread.start()
145
146 def stop_serving(self):
147 self.shutdown()
148 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000149
mattm@chromium.org830a3712012-11-07 23:00:07 +0000150
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000151class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000152 testserver_base.ClientRestrictingServerMixIn,
153 testserver_base.BrokenPipeHandlerMixIn,
154 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000155 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000156 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000157
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000158 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000159 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700160 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
161 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000162 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700163 fallback_scsv_enabled, ocsp_response,
David Benjaminf839f1c2018-10-16 06:01:29 +0000164 alert_after_handshake, disable_channel_id, disable_ems,
165 simulate_tls13_downgrade, simulate_tls12_downgrade,
166 tls_max_version):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000167 self.cert_chain = tlslite.api.X509CertChain()
168 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000169 # Force using only python implementation - otherwise behavior is different
170 # depending on whether m2crypto Python module is present (error is thrown
171 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
172 # the hood.
173 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
174 private=True,
175 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000176 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000177 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000178 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700179 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000180 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000181 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000182 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000183
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000184 if ssl_client_auth:
185 for ca_file in ssl_client_cas:
186 s = open(ca_file).read()
187 x509 = tlslite.api.X509()
188 x509.parse(s)
189 self.ssl_client_cas.append(x509.subject)
190
191 for cert_type in ssl_client_cert_types:
192 self.ssl_client_cert_types.append({
193 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000194 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
195 }[cert_type])
196
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000197 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800198 # Enable SSLv3 for testing purposes.
199 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000200 if ssl_bulk_ciphers is not None:
201 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000202 if ssl_key_exchanges is not None:
203 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000204 if tls_intolerant != 0:
205 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
206 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700207 if alert_after_handshake:
208 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700209 if disable_channel_id:
210 self.ssl_handshake_settings.enableChannelID = False
211 if disable_ems:
212 self.ssl_handshake_settings.enableExtendedMasterSecret = False
David Benjaminf839f1c2018-10-16 06:01:29 +0000213 if simulate_tls13_downgrade:
214 self.ssl_handshake_settings.simulateTLS13Downgrade = True
215 if simulate_tls12_downgrade:
216 self.ssl_handshake_settings.simulateTLS12Downgrade = True
217 if tls_max_version != 0:
218 self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
bnc5fb33bd2016-08-05 12:09:21 -0700219 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000220
rsleevi8146efa2015-03-16 12:31:24 -0700221 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000222 # If record_resume_info is true then we'll replace the session cache with
223 # an object that records the lookups and inserts that it sees.
224 self.session_cache = RecordingSSLSessionCache()
225 else:
226 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000227 testserver_base.StoppableHTTPServer.__init__(self,
228 server_address,
229 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000230
231 def handshake(self, tlsConnection):
232 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000233
initial.commit94958cf2008-07-26 22:42:52 +0000234 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000235 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000236 tlsConnection.handshakeServer(certChain=self.cert_chain,
237 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000238 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000239 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000240 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000241 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000242 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700243 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000244 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000245 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000246 fallbackSCSV=self.fallback_scsv_enabled,
247 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000248 tlsConnection.ignoreAbruptClose = True
249 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000250 except tlslite.api.TLSAbruptCloseError:
251 # Ignore abrupt close.
252 return True
initial.commit94958cf2008-07-26 22:42:52 +0000253 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000254 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000255 return False
256
akalin@chromium.org154bb132010-11-12 02:20:27 +0000257
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000258class FTPServer(testserver_base.ClientRestrictingServerMixIn,
259 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000260 """This is a specialization of FTPServer that adds client verification."""
261
262 pass
263
264
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000265class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
266 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000267 """A TCP echo server that echoes back what it has received."""
268
269 def server_bind(self):
270 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000271
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000272 SocketServer.TCPServer.server_bind(self)
273 host, port = self.socket.getsockname()[:2]
274 self.server_name = socket.getfqdn(host)
275 self.server_port = port
276
277 def serve_forever(self):
278 self.stop = False
279 self.nonce_time = None
280 while not self.stop:
281 self.handle_request()
282 self.socket.close()
283
284
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000285class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
286 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000287 """A UDP echo server that echoes back what it has received."""
288
289 def server_bind(self):
290 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000291
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000292 SocketServer.UDPServer.server_bind(self)
293 host, port = self.socket.getsockname()[:2]
294 self.server_name = socket.getfqdn(host)
295 self.server_port = port
296
297 def serve_forever(self):
298 self.stop = False
299 self.nonce_time = None
300 while not self.stop:
301 self.handle_request()
302 self.socket.close()
303
304
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000305class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000306 # Class variables to allow for persistence state between page handler
307 # invocations
308 rst_limits = {}
309 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000310
311 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000312 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000313 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000314 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000315 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000316 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000317 self.NoCacheMaxAgeTimeHandler,
318 self.NoCacheTimeHandler,
319 self.CacheTimeHandler,
320 self.CacheExpiresHandler,
321 self.CacheProxyRevalidateHandler,
322 self.CachePrivateHandler,
323 self.CachePublicHandler,
324 self.CacheSMaxAgeHandler,
325 self.CacheMustRevalidateHandler,
326 self.CacheMustRevalidateMaxAgeHandler,
327 self.CacheNoStoreHandler,
328 self.CacheNoStoreMaxAgeHandler,
329 self.CacheNoTransformHandler,
330 self.DownloadHandler,
331 self.DownloadFinishHandler,
332 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000333 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000334 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000335 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000336 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000337 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000338 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000339 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000340 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000341 self.AuthBasicHandler,
342 self.AuthDigestHandler,
343 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000344 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000345 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000346 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700347 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000348 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000349 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000350 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000351 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700352 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700353 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000354 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000355 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000356 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000357 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000358 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000359 self.PostOnlyFileHandler,
360 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000361 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000362 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000363 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000364 head_handlers = [
365 self.FileHandler,
366 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000367
maruel@google.come250a9b2009-03-10 17:39:46 +0000368 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000369 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000370 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000371 'gif': 'image/gif',
372 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000373 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700374 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000375 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000376 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000377 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000378 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000379 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000380 }
initial.commit94958cf2008-07-26 22:42:52 +0000381 self._default_mime_type = 'text/html'
382
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000383 testserver_base.BasePageHandler.__init__(self, request, client_address,
384 socket_server, connect_handlers,
385 get_handlers, head_handlers,
386 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000387
initial.commit94958cf2008-07-26 22:42:52 +0000388 def GetMIMETypeFromName(self, file_name):
389 """Returns the mime type for the specified file_name. So far it only looks
390 at the file extension."""
391
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000392 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000393 if len(extension) == 0:
394 # no extension.
395 return self._default_mime_type
396
ericroman@google.comc17ca532009-05-07 03:51:05 +0000397 # extension starts with a dot, so we need to remove it
398 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000399
initial.commit94958cf2008-07-26 22:42:52 +0000400 def NoCacheMaxAgeTimeHandler(self):
401 """This request handler yields a page with the title set to the current
402 system time, and no caching requested."""
403
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000404 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000405 return False
406
407 self.send_response(200)
408 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000409 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000410 self.end_headers()
411
maruel@google.come250a9b2009-03-10 17:39:46 +0000412 self.wfile.write('<html><head><title>%s</title></head></html>' %
413 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000414
415 return True
416
417 def NoCacheTimeHandler(self):
418 """This request handler yields a page with the title set to the current
419 system time, and no caching requested."""
420
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000421 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000422 return False
423
424 self.send_response(200)
425 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000426 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000427 self.end_headers()
428
maruel@google.come250a9b2009-03-10 17:39:46 +0000429 self.wfile.write('<html><head><title>%s</title></head></html>' %
430 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000431
432 return True
433
434 def CacheTimeHandler(self):
435 """This request handler yields a page with the title set to the current
436 system time, and allows caching for one minute."""
437
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000438 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000439 return False
440
441 self.send_response(200)
442 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000443 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000444 self.end_headers()
445
maruel@google.come250a9b2009-03-10 17:39:46 +0000446 self.wfile.write('<html><head><title>%s</title></head></html>' %
447 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000448
449 return True
450
451 def CacheExpiresHandler(self):
452 """This request handler yields a page with the title set to the current
453 system time, and set the page to expire on 1 Jan 2099."""
454
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000455 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000456 return False
457
458 self.send_response(200)
459 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000460 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000461 self.end_headers()
462
maruel@google.come250a9b2009-03-10 17:39:46 +0000463 self.wfile.write('<html><head><title>%s</title></head></html>' %
464 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000465
466 return True
467
468 def CacheProxyRevalidateHandler(self):
469 """This request handler yields a page with the title set to the current
470 system time, and allows caching for 60 seconds"""
471
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000472 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000473 return False
474
475 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000476 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000477 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
478 self.end_headers()
479
maruel@google.come250a9b2009-03-10 17:39:46 +0000480 self.wfile.write('<html><head><title>%s</title></head></html>' %
481 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000482
483 return True
484
485 def CachePrivateHandler(self):
486 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700487 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000488
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000489 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000490 return False
491
492 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000493 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000494 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000495 self.end_headers()
496
maruel@google.come250a9b2009-03-10 17:39:46 +0000497 self.wfile.write('<html><head><title>%s</title></head></html>' %
498 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000499
500 return True
501
502 def CachePublicHandler(self):
503 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700504 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000505
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000506 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000507 return False
508
509 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000510 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000511 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000512 self.end_headers()
513
maruel@google.come250a9b2009-03-10 17:39:46 +0000514 self.wfile.write('<html><head><title>%s</title></head></html>' %
515 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000516
517 return True
518
519 def CacheSMaxAgeHandler(self):
520 """This request handler yields a page with the title set to the current
521 system time, and does not allow for caching."""
522
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000523 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000524 return False
525
526 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000527 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000528 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
529 self.end_headers()
530
maruel@google.come250a9b2009-03-10 17:39:46 +0000531 self.wfile.write('<html><head><title>%s</title></head></html>' %
532 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000533
534 return True
535
536 def CacheMustRevalidateHandler(self):
537 """This request handler yields a page with the title set to the current
538 system time, and does not allow caching."""
539
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000540 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000541 return False
542
543 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000544 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000545 self.send_header('Cache-Control', 'must-revalidate')
546 self.end_headers()
547
maruel@google.come250a9b2009-03-10 17:39:46 +0000548 self.wfile.write('<html><head><title>%s</title></head></html>' %
549 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000550
551 return True
552
553 def CacheMustRevalidateMaxAgeHandler(self):
554 """This request handler yields a page with the title set to the current
555 system time, and does not allow caching event though max-age of 60
556 seconds is specified."""
557
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000558 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000559 return False
560
561 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000562 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000563 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
564 self.end_headers()
565
maruel@google.come250a9b2009-03-10 17:39:46 +0000566 self.wfile.write('<html><head><title>%s</title></head></html>' %
567 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000568
569 return True
570
initial.commit94958cf2008-07-26 22:42:52 +0000571 def CacheNoStoreHandler(self):
572 """This request handler yields a page with the title set to the current
573 system time, and does not allow the page to be stored."""
574
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000575 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000576 return False
577
578 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000579 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000580 self.send_header('Cache-Control', 'no-store')
581 self.end_headers()
582
maruel@google.come250a9b2009-03-10 17:39:46 +0000583 self.wfile.write('<html><head><title>%s</title></head></html>' %
584 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000585
586 return True
587
588 def CacheNoStoreMaxAgeHandler(self):
589 """This request handler yields a page with the title set to the current
590 system time, and does not allow the page to be stored even though max-age
591 of 60 seconds is specified."""
592
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000593 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000594 return False
595
596 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000597 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000598 self.send_header('Cache-Control', 'max-age=60, no-store')
599 self.end_headers()
600
maruel@google.come250a9b2009-03-10 17:39:46 +0000601 self.wfile.write('<html><head><title>%s</title></head></html>' %
602 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000603
604 return True
605
606
607 def CacheNoTransformHandler(self):
608 """This request handler yields a page with the title set to the current
609 system time, and does not allow the content to transformed during
610 user-agent caching"""
611
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000612 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000613 return False
614
615 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000616 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000617 self.send_header('Cache-Control', 'no-transform')
618 self.end_headers()
619
maruel@google.come250a9b2009-03-10 17:39:46 +0000620 self.wfile.write('<html><head><title>%s</title></head></html>' %
621 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000622
623 return True
624
625 def EchoHeader(self):
626 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000627
ananta@chromium.org219b2062009-10-23 16:09:41 +0000628 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000629
ananta@chromium.org56812d02011-04-07 17:52:05 +0000630 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000631 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000632 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000633
ananta@chromium.org56812d02011-04-07 17:52:05 +0000634 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000635
636 def EchoHeaderHelper(self, echo_header):
637 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000638
ananta@chromium.org219b2062009-10-23 16:09:41 +0000639 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000640 return False
641
642 query_char = self.path.find('?')
643 if query_char != -1:
644 header_name = self.path[query_char+1:]
645
646 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000647 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000648 if echo_header == '/echoheadercache':
649 self.send_header('Cache-control', 'max-age=60000')
650 else:
651 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000652 # insert a vary header to properly indicate that the cachability of this
653 # request is subject to value of the request header being echoed.
654 if len(header_name) > 0:
655 self.send_header('Vary', header_name)
656 self.end_headers()
657
658 if len(header_name) > 0:
659 self.wfile.write(self.headers.getheader(header_name))
660
661 return True
662
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000663 def ReadRequestBody(self):
664 """This function reads the body of the current HTTP request, handling
665 both plain and chunked transfer encoded requests."""
666
667 if self.headers.getheader('transfer-encoding') != 'chunked':
668 length = int(self.headers.getheader('content-length'))
669 return self.rfile.read(length)
670
671 # Read the request body as chunks.
672 body = ""
673 while True:
674 line = self.rfile.readline()
675 length = int(line, 16)
676 if length == 0:
677 self.rfile.readline()
678 break
679 body += self.rfile.read(length)
680 self.rfile.read(2)
681 return body
682
initial.commit94958cf2008-07-26 22:42:52 +0000683 def EchoHandler(self):
684 """This handler just echoes back the payload of the request, for testing
685 form submission."""
686
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000687 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000688 return False
689
hirono2838c572015-01-21 12:18:11 -0800690 _, _, _, _, query, _ = urlparse.urlparse(self.path)
691 query_params = cgi.parse_qs(query, True)
692 if 'status' in query_params:
693 self.send_response(int(query_params['status'][0]))
694 else:
695 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000696 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000697 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000698 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000699 return True
700
701 def EchoTitleHandler(self):
702 """This handler is like Echo, but sets the page title to the request."""
703
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000704 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000705 return False
706
707 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000708 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000709 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000710 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000711 self.wfile.write('<html><head><title>')
712 self.wfile.write(request)
713 self.wfile.write('</title></head></html>')
714 return True
715
716 def EchoAllHandler(self):
717 """This handler yields a (more) human-readable page listing information
718 about the request header & contents."""
719
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000720 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000721 return False
722
723 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000724 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000725 self.end_headers()
726 self.wfile.write('<html><head><style>'
727 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
728 '</style></head><body>'
729 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000730 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000731 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000732
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000733 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000734 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000735 params = cgi.parse_qs(qs, keep_blank_values=1)
736
737 for param in params:
738 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000739
740 self.wfile.write('</pre>')
741
742 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
743
744 self.wfile.write('</body></html>')
745 return True
746
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000747 def EchoMultipartPostHandler(self):
748 """This handler echoes received multipart post data as json format."""
749
750 if not (self._ShouldHandleRequest("/echomultipartpost") or
751 self._ShouldHandleRequest("/searchbyimage")):
752 return False
753
754 content_type, parameters = cgi.parse_header(
755 self.headers.getheader('content-type'))
756 if content_type == 'multipart/form-data':
757 post_multipart = cgi.parse_multipart(self.rfile, parameters)
758 elif content_type == 'application/x-www-form-urlencoded':
759 raise Exception('POST by application/x-www-form-urlencoded is '
760 'not implemented.')
761 else:
762 post_multipart = {}
763
764 # Since the data can be binary, we encode them by base64.
765 post_multipart_base64_encoded = {}
766 for field, values in post_multipart.items():
767 post_multipart_base64_encoded[field] = [base64.b64encode(value)
768 for value in values]
769
770 result = {'POST_multipart' : post_multipart_base64_encoded}
771
772 self.send_response(200)
773 self.send_header("Content-type", "text/plain")
774 self.end_headers()
775 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
776 return True
777
initial.commit94958cf2008-07-26 22:42:52 +0000778 def DownloadHandler(self):
779 """This handler sends a downloadable file with or without reporting
780 the size (6K)."""
781
782 if self.path.startswith("/download-unknown-size"):
783 send_length = False
784 elif self.path.startswith("/download-known-size"):
785 send_length = True
786 else:
787 return False
788
789 #
790 # The test which uses this functionality is attempting to send
791 # small chunks of data to the client. Use a fairly large buffer
792 # so that we'll fill chrome's IO buffer enough to force it to
793 # actually write the data.
794 # See also the comments in the client-side of this test in
795 # download_uitest.cc
796 #
797 size_chunk1 = 35*1024
798 size_chunk2 = 10*1024
799
800 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000801 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000802 self.send_header('Cache-Control', 'max-age=0')
803 if send_length:
804 self.send_header('Content-Length', size_chunk1 + size_chunk2)
805 self.end_headers()
806
807 # First chunk of data:
808 self.wfile.write("*" * size_chunk1)
809 self.wfile.flush()
810
811 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000812 self.server.wait_for_download = True
813 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000814 self.server.handle_request()
815
816 # Second chunk of data:
817 self.wfile.write("*" * size_chunk2)
818 return True
819
820 def DownloadFinishHandler(self):
821 """This handler just tells the server to finish the current download."""
822
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000823 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000824 return False
825
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000826 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000827 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000828 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000829 self.send_header('Cache-Control', 'max-age=0')
830 self.end_headers()
831 return True
832
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000833 def _ReplaceFileData(self, data, query_parameters):
834 """Replaces matching substrings in a file.
835
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000836 If the 'replace_text' URL query parameter is present, it is expected to be
837 of the form old_text:new_text, which indicates that any old_text strings in
838 the file are replaced with new_text. Multiple 'replace_text' parameters may
839 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000840
841 If the parameters are not present, |data| is returned.
842 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000843
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000844 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000845 replace_text_values = query_dict.get('replace_text', [])
846 for replace_text_value in replace_text_values:
847 replace_text_args = replace_text_value.split(':')
848 if len(replace_text_args) != 2:
849 raise ValueError(
850 'replace_text must be of form old_text:new_text. Actual value: %s' %
851 replace_text_value)
852 old_text_b64, new_text_b64 = replace_text_args
853 old_text = base64.urlsafe_b64decode(old_text_b64)
854 new_text = base64.urlsafe_b64decode(new_text_b64)
855 data = data.replace(old_text, new_text)
856 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000857
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000858 def ZipFileHandler(self):
859 """This handler sends the contents of the requested file in compressed form.
860 Can pass in a parameter that specifies that the content length be
861 C - the compressed size (OK),
862 U - the uncompressed size (Non-standard, but handled),
863 S - less than compressed (OK because we keep going),
864 M - larger than compressed but less than uncompressed (an error),
865 L - larger than uncompressed (an error)
866 Example: compressedfiles/Picture_1.doc?C
867 """
868
869 prefix = "/compressedfiles/"
870 if not self.path.startswith(prefix):
871 return False
872
873 # Consume a request body if present.
874 if self.command == 'POST' or self.command == 'PUT' :
875 self.ReadRequestBody()
876
877 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
878
879 if not query in ('C', 'U', 'S', 'M', 'L'):
880 return False
881
882 sub_path = url_path[len(prefix):]
883 entries = sub_path.split('/')
884 file_path = os.path.join(self.server.data_dir, *entries)
885 if os.path.isdir(file_path):
886 file_path = os.path.join(file_path, 'index.html')
887
888 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000889 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000890 self.send_error(404)
891 return True
892
893 f = open(file_path, "rb")
894 data = f.read()
895 uncompressed_len = len(data)
896 f.close()
897
898 # Compress the data.
899 data = zlib.compress(data)
900 compressed_len = len(data)
901
902 content_length = compressed_len
903 if query == 'U':
904 content_length = uncompressed_len
905 elif query == 'S':
906 content_length = compressed_len / 2
907 elif query == 'M':
908 content_length = (compressed_len + uncompressed_len) / 2
909 elif query == 'L':
910 content_length = compressed_len + uncompressed_len
911
912 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000913 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000914 self.send_header('Content-encoding', 'deflate')
915 self.send_header('Connection', 'close')
916 self.send_header('Content-Length', content_length)
917 self.send_header('ETag', '\'' + file_path + '\'')
918 self.end_headers()
919
920 self.wfile.write(data)
921
922 return True
923
initial.commit94958cf2008-07-26 22:42:52 +0000924 def FileHandler(self):
925 """This handler sends the contents of the requested file. Wow, it's like
926 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000927
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000928 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000929 if not self.path.startswith(prefix):
930 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000931 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000932
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000933 def PostOnlyFileHandler(self):
934 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000935
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000936 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000937 if not self.path.startswith(prefix):
938 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000939 return self._FileHandlerHelper(prefix)
940
941 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000942 request_body = ''
943 if self.command == 'POST' or self.command == 'PUT':
944 # Consume a request body if present.
945 request_body = self.ReadRequestBody()
946
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000947 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000948 query_dict = cgi.parse_qs(query)
949
950 expected_body = query_dict.get('expected_body', [])
951 if expected_body and request_body not in expected_body:
952 self.send_response(404)
953 self.end_headers()
954 self.wfile.write('')
955 return True
956
957 expected_headers = query_dict.get('expected_headers', [])
958 for expected_header in expected_headers:
959 header_name, expected_value = expected_header.split(':')
960 if self.headers.getheader(header_name) != expected_value:
961 self.send_response(404)
962 self.end_headers()
963 self.wfile.write('')
964 return True
965
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000966 sub_path = url_path[len(prefix):]
967 entries = sub_path.split('/')
968 file_path = os.path.join(self.server.data_dir, *entries)
969 if os.path.isdir(file_path):
970 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000971
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000972 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000973 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000974 self.send_error(404)
975 return True
976
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000977 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000978 data = f.read()
979 f.close()
980
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000981 data = self._ReplaceFileData(data, query)
982
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000983 old_protocol_version = self.protocol_version
984
initial.commit94958cf2008-07-26 22:42:52 +0000985 # If file.mock-http-headers exists, it contains the headers we
986 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000987 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000988 if os.path.isfile(headers_path):
989 f = open(headers_path, "r")
990
991 # "HTTP/1.1 200 OK"
992 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000993 http_major, http_minor, status_code = re.findall(
994 'HTTP/(\d+).(\d+) (\d+)', response)[0]
995 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000996 self.send_response(int(status_code))
997
998 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000999 header_values = re.findall('(\S+):\s*(.*)', line)
1000 if len(header_values) > 0:
1001 # "name: value"
1002 name, value = header_values[0]
1003 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001004 f.close()
1005 else:
1006 # Could be more generic once we support mime-type sniffing, but for
1007 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001008
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001009 range_header = self.headers.get('Range')
1010 if range_header and range_header.startswith('bytes='):
1011 # Note this doesn't handle all valid byte range_header values (i.e.
1012 # left open ended ones), just enough for what we needed so far.
1013 range_header = range_header[6:].split('-')
1014 start = int(range_header[0])
1015 if range_header[1]:
1016 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001017 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001018 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001019
1020 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001021 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1022 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001023 self.send_header('Content-Range', content_range)
1024 data = data[start: end + 1]
1025 else:
1026 self.send_response(200)
1027
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001028 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001029 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001030 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001031 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001032 self.end_headers()
1033
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001034 if (self.command != 'HEAD'):
1035 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001036
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001037 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001038 return True
1039
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001040 def SetCookieHandler(self):
1041 """This handler just sets a cookie, for testing cookie handling."""
1042
1043 if not self._ShouldHandleRequest("/set-cookie"):
1044 return False
1045
1046 query_char = self.path.find('?')
1047 if query_char != -1:
1048 cookie_values = self.path[query_char + 1:].split('&')
1049 else:
1050 cookie_values = ("",)
1051 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001052 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001053 for cookie_value in cookie_values:
1054 self.send_header('Set-Cookie', '%s' % cookie_value)
1055 self.end_headers()
1056 for cookie_value in cookie_values:
1057 self.wfile.write('%s' % cookie_value)
1058 return True
1059
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001060 def SetManyCookiesHandler(self):
1061 """This handler just sets a given number of cookies, for testing handling
1062 of large numbers of cookies."""
1063
1064 if not self._ShouldHandleRequest("/set-many-cookies"):
1065 return False
1066
1067 query_char = self.path.find('?')
1068 if query_char != -1:
1069 num_cookies = int(self.path[query_char + 1:])
1070 else:
1071 num_cookies = 0
1072 self.send_response(200)
1073 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001074 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001075 self.send_header('Set-Cookie', 'a=')
1076 self.end_headers()
1077 self.wfile.write('%d cookies were sent' % num_cookies)
1078 return True
1079
mattm@chromium.org983fc462012-06-30 00:52:08 +00001080 def ExpectAndSetCookieHandler(self):
1081 """Expects some cookies to be sent, and if they are, sets more cookies.
1082
1083 The expect parameter specifies a required cookie. May be specified multiple
1084 times.
1085 The set parameter specifies a cookie to set if all required cookies are
1086 preset. May be specified multiple times.
1087 The data parameter specifies the response body data to be returned."""
1088
1089 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1090 return False
1091
1092 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1093 query_dict = cgi.parse_qs(query)
1094 cookies = set()
1095 if 'Cookie' in self.headers:
1096 cookie_header = self.headers.getheader('Cookie')
1097 cookies.update([s.strip() for s in cookie_header.split(';')])
1098 got_all_expected_cookies = True
1099 for expected_cookie in query_dict.get('expect', []):
1100 if expected_cookie not in cookies:
1101 got_all_expected_cookies = False
1102 self.send_response(200)
1103 self.send_header('Content-Type', 'text/html')
1104 if got_all_expected_cookies:
1105 for cookie_value in query_dict.get('set', []):
1106 self.send_header('Set-Cookie', '%s' % cookie_value)
1107 self.end_headers()
1108 for data_value in query_dict.get('data', []):
1109 self.wfile.write(data_value)
1110 return True
1111
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001112 def SetHeaderHandler(self):
1113 """This handler sets a response header. Parameters are in the
1114 key%3A%20value&key2%3A%20value2 format."""
1115
1116 if not self._ShouldHandleRequest("/set-header"):
1117 return False
1118
1119 query_char = self.path.find('?')
1120 if query_char != -1:
1121 headers_values = self.path[query_char + 1:].split('&')
1122 else:
1123 headers_values = ("",)
1124 self.send_response(200)
1125 self.send_header('Content-Type', 'text/html')
1126 for header_value in headers_values:
1127 header_value = urllib.unquote(header_value)
1128 (key, value) = header_value.split(': ', 1)
1129 self.send_header(key, value)
1130 self.end_headers()
1131 for header_value in headers_values:
1132 self.wfile.write('%s' % header_value)
1133 return True
1134
initial.commit94958cf2008-07-26 22:42:52 +00001135 def AuthBasicHandler(self):
1136 """This handler tests 'Basic' authentication. It just sends a page with
1137 title 'user/pass' if you succeed."""
1138
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001139 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001140 return False
1141
1142 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001143 expected_password = 'secret'
1144 realm = 'testrealm'
1145 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001146
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001147 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1148 query_params = cgi.parse_qs(query, True)
1149 if 'set-cookie-if-challenged' in query_params:
1150 set_cookie_if_challenged = True
1151 if 'password' in query_params:
1152 expected_password = query_params['password'][0]
1153 if 'realm' in query_params:
1154 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001155
initial.commit94958cf2008-07-26 22:42:52 +00001156 auth = self.headers.getheader('authorization')
1157 try:
1158 if not auth:
1159 raise Exception('no auth')
1160 b64str = re.findall(r'Basic (\S+)', auth)[0]
1161 userpass = base64.b64decode(b64str)
1162 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001163 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001164 raise Exception('wrong password')
1165 except Exception, e:
1166 # Authentication failed.
1167 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001168 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001169 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001170 if set_cookie_if_challenged:
1171 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001172 self.end_headers()
1173 self.wfile.write('<html><head>')
1174 self.wfile.write('<title>Denied: %s</title>' % e)
1175 self.wfile.write('</head><body>')
1176 self.wfile.write('auth=%s<p>' % auth)
1177 self.wfile.write('b64str=%s<p>' % b64str)
1178 self.wfile.write('username: %s<p>' % username)
1179 self.wfile.write('userpass: %s<p>' % userpass)
1180 self.wfile.write('password: %s<p>' % password)
1181 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1182 self.wfile.write('</body></html>')
1183 return True
1184
1185 # Authentication successful. (Return a cachable response to allow for
1186 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001187 old_protocol_version = self.protocol_version
1188 self.protocol_version = "HTTP/1.1"
1189
initial.commit94958cf2008-07-26 22:42:52 +00001190 if_none_match = self.headers.getheader('if-none-match')
1191 if if_none_match == "abc":
1192 self.send_response(304)
1193 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001194 elif url_path.endswith(".gif"):
1195 # Using chrome/test/data/google/logo.gif as the test image
1196 test_image_path = ['google', 'logo.gif']
1197 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1198 if not os.path.isfile(gif_path):
1199 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001200 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001201 return True
1202
1203 f = open(gif_path, "rb")
1204 data = f.read()
1205 f.close()
1206
1207 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001208 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001209 self.send_header('Cache-control', 'max-age=60000')
1210 self.send_header('Etag', 'abc')
1211 self.end_headers()
1212 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001213 else:
1214 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001215 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001216 self.send_header('Cache-control', 'max-age=60000')
1217 self.send_header('Etag', 'abc')
1218 self.end_headers()
1219 self.wfile.write('<html><head>')
1220 self.wfile.write('<title>%s/%s</title>' % (username, password))
1221 self.wfile.write('</head><body>')
1222 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001223 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001224 self.wfile.write('</body></html>')
1225
rvargas@google.com54453b72011-05-19 01:11:11 +00001226 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001227 return True
1228
tonyg@chromium.org75054202010-03-31 22:06:10 +00001229 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001230 """Returns a nonce that's stable per request path for the server's lifetime.
1231 This is a fake implementation. A real implementation would only use a given
1232 nonce a single time (hence the name n-once). However, for the purposes of
1233 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001234
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001235 Args:
1236 force_reset: Iff set, the nonce will be changed. Useful for testing the
1237 "stale" response.
1238 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001239
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001240 if force_reset or not self.server.nonce_time:
1241 self.server.nonce_time = time.time()
1242 return hashlib.md5('privatekey%s%d' %
1243 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001244
1245 def AuthDigestHandler(self):
1246 """This handler tests 'Digest' authentication.
1247
1248 It just sends a page with title 'user/pass' if you succeed.
1249
1250 A stale response is sent iff "stale" is present in the request path.
1251 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001252
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001253 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001254 return False
1255
tonyg@chromium.org75054202010-03-31 22:06:10 +00001256 stale = 'stale' in self.path
1257 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001258 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001259 password = 'secret'
1260 realm = 'testrealm'
1261
1262 auth = self.headers.getheader('authorization')
1263 pairs = {}
1264 try:
1265 if not auth:
1266 raise Exception('no auth')
1267 if not auth.startswith('Digest'):
1268 raise Exception('not digest')
1269 # Pull out all the name="value" pairs as a dictionary.
1270 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1271
1272 # Make sure it's all valid.
1273 if pairs['nonce'] != nonce:
1274 raise Exception('wrong nonce')
1275 if pairs['opaque'] != opaque:
1276 raise Exception('wrong opaque')
1277
1278 # Check the 'response' value and make sure it matches our magic hash.
1279 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001280 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001281 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001282 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001283 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001284 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001285 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1286 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001287 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001288
1289 if pairs['response'] != response:
1290 raise Exception('wrong password')
1291 except Exception, e:
1292 # Authentication failed.
1293 self.send_response(401)
1294 hdr = ('Digest '
1295 'realm="%s", '
1296 'domain="/", '
1297 'qop="auth", '
1298 'algorithm=MD5, '
1299 'nonce="%s", '
1300 'opaque="%s"') % (realm, nonce, opaque)
1301 if stale:
1302 hdr += ', stale="TRUE"'
1303 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001304 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001305 self.end_headers()
1306 self.wfile.write('<html><head>')
1307 self.wfile.write('<title>Denied: %s</title>' % e)
1308 self.wfile.write('</head><body>')
1309 self.wfile.write('auth=%s<p>' % auth)
1310 self.wfile.write('pairs=%s<p>' % pairs)
1311 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1312 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1313 self.wfile.write('</body></html>')
1314 return True
1315
1316 # Authentication successful.
1317 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001318 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001319 self.end_headers()
1320 self.wfile.write('<html><head>')
1321 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1322 self.wfile.write('</head><body>')
1323 self.wfile.write('auth=%s<p>' % auth)
1324 self.wfile.write('pairs=%s<p>' % pairs)
1325 self.wfile.write('</body></html>')
1326
1327 return True
1328
1329 def SlowServerHandler(self):
1330 """Wait for the user suggested time before responding. The syntax is
1331 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001332
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001333 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001334 return False
1335 query_char = self.path.find('?')
1336 wait_sec = 1.0
1337 if query_char >= 0:
1338 try:
davidben05f82202015-03-31 13:48:07 -07001339 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001340 except ValueError:
1341 pass
1342 time.sleep(wait_sec)
1343 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001344 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001345 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001346 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001347 return True
1348
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001349 def ChunkedServerHandler(self):
1350 """Send chunked response. Allows to specify chunks parameters:
1351 - waitBeforeHeaders - ms to wait before sending headers
1352 - waitBetweenChunks - ms to wait between chunks
1353 - chunkSize - size of each chunk in bytes
1354 - chunksNumber - number of chunks
1355 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1356 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001357
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001358 if not self._ShouldHandleRequest("/chunked"):
1359 return False
1360 query_char = self.path.find('?')
1361 chunkedSettings = {'waitBeforeHeaders' : 0,
1362 'waitBetweenChunks' : 0,
1363 'chunkSize' : 5,
1364 'chunksNumber' : 5}
1365 if query_char >= 0:
1366 params = self.path[query_char + 1:].split('&')
1367 for param in params:
1368 keyValue = param.split('=')
1369 if len(keyValue) == 2:
1370 try:
1371 chunkedSettings[keyValue[0]] = int(keyValue[1])
1372 except ValueError:
1373 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001374 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001375 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1376 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001377 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001378 self.send_header('Connection', 'close')
1379 self.send_header('Transfer-Encoding', 'chunked')
1380 self.end_headers()
1381 # Chunked encoding: sending all chunks, then final zero-length chunk and
1382 # then final CRLF.
1383 for i in range(0, chunkedSettings['chunksNumber']):
1384 if i > 0:
1385 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1386 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001387 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001388 self.sendChunkHelp('')
1389 return True
1390
creis@google.com2f4f6a42011-03-25 19:44:19 +00001391 def NoContentHandler(self):
1392 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001393
creis@google.com2f4f6a42011-03-25 19:44:19 +00001394 if not self._ShouldHandleRequest("/nocontent"):
1395 return False
1396 self.send_response(204)
1397 self.end_headers()
1398 return True
1399
initial.commit94958cf2008-07-26 22:42:52 +00001400 def ServerRedirectHandler(self):
1401 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001402 '/server-redirect?http://foo.bar/asdf' to redirect to
1403 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001404
1405 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001406 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001407 return False
1408
1409 query_char = self.path.find('?')
1410 if query_char < 0 or len(self.path) <= query_char + 1:
1411 self.sendRedirectHelp(test_name)
1412 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001413 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001414
1415 self.send_response(301) # moved permanently
1416 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001417 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001418 self.end_headers()
1419 self.wfile.write('<html><head>')
1420 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1421
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001422 return True
initial.commit94958cf2008-07-26 22:42:52 +00001423
naskoe7a0d0d2014-09-29 08:53:05 -07001424 def CrossSiteRedirectHandler(self):
1425 """Sends a server redirect to the given site. The syntax is
1426 '/cross-site/hostname/...' to redirect to //hostname/...
1427 It is used to navigate between different Sites, causing
1428 cross-site/cross-process navigations in the browser."""
1429
1430 test_name = "/cross-site"
1431 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001432 return False
1433
1434 params = urllib.unquote(self.path[(len(test_name) + 1):])
1435 slash = params.find('/')
1436 if slash < 0:
1437 self.sendRedirectHelp(test_name)
1438 return True
1439
1440 host = params[:slash]
1441 path = params[(slash+1):]
1442 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1443
1444 self.send_response(301) # moved permanently
1445 self.send_header('Location', dest)
1446 self.send_header('Content-Type', 'text/html')
1447 self.end_headers()
1448 self.wfile.write('<html><head>')
1449 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1450
1451 return True
1452
initial.commit94958cf2008-07-26 22:42:52 +00001453 def ClientRedirectHandler(self):
1454 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001455 '/client-redirect?http://foo.bar/asdf' to redirect to
1456 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001457
1458 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001459 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001460 return False
1461
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001462 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001463 if query_char < 0 or len(self.path) <= query_char + 1:
1464 self.sendRedirectHelp(test_name)
1465 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001466 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001467
1468 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001469 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001470 self.end_headers()
1471 self.wfile.write('<html><head>')
1472 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1473 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1474
1475 return True
1476
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001477 def GetSSLSessionCacheHandler(self):
1478 """Send a reply containing a log of the session cache operations."""
1479
1480 if not self._ShouldHandleRequest('/ssl-session-cache'):
1481 return False
1482
1483 self.send_response(200)
1484 self.send_header('Content-Type', 'text/plain')
1485 self.end_headers()
1486 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001487 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001488 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001489 self.wfile.write('Pass --https-record-resume in order to use' +
1490 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001491 return True
1492
1493 for (action, sessionID) in log:
1494 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001495 return True
1496
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001497 def SSLManySmallRecords(self):
1498 """Sends a reply consisting of a variety of small writes. These will be
1499 translated into a series of small SSL records when used over an HTTPS
1500 server."""
1501
1502 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1503 return False
1504
1505 self.send_response(200)
1506 self.send_header('Content-Type', 'text/plain')
1507 self.end_headers()
1508
1509 # Write ~26K of data, in 1350 byte chunks
1510 for i in xrange(20):
1511 self.wfile.write('*' * 1350)
1512 self.wfile.flush()
1513 return True
1514
agl@chromium.org04700be2013-03-02 18:40:41 +00001515 def GetChannelID(self):
1516 """Send a reply containing the hashed ChannelID that the client provided."""
1517
1518 if not self._ShouldHandleRequest('/channel-id'):
1519 return False
1520
1521 self.send_response(200)
1522 self.send_header('Content-Type', 'text/plain')
1523 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001524 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001525 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1526 return True
1527
pneubeckfd4f0442015-08-07 04:55:10 -07001528 def GetClientCert(self):
1529 """Send a reply whether a client certificate was provided."""
1530
1531 if not self._ShouldHandleRequest('/client-cert'):
1532 return False
1533
1534 self.send_response(200)
1535 self.send_header('Content-Type', 'text/plain')
1536 self.end_headers()
1537
1538 cert_chain = self.server.tlsConnection.session.clientCertChain
1539 if cert_chain != None:
1540 self.wfile.write('got client cert with fingerprint: ' +
1541 cert_chain.getFingerprint())
1542 else:
1543 self.wfile.write('got no client cert')
1544 return True
1545
davidben599e7e72014-09-03 16:19:09 -07001546 def ClientCipherListHandler(self):
1547 """Send a reply containing the cipher suite list that the client
1548 provided. Each cipher suite value is serialized in decimal, followed by a
1549 newline."""
1550
1551 if not self._ShouldHandleRequest('/client-cipher-list'):
1552 return False
1553
1554 self.send_response(200)
1555 self.send_header('Content-Type', 'text/plain')
1556 self.end_headers()
1557
davidben11682512014-10-06 21:09:11 -07001558 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1559 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001560 return True
1561
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001562 def CloseSocketHandler(self):
1563 """Closes the socket without sending anything."""
1564
1565 if not self._ShouldHandleRequest('/close-socket'):
1566 return False
1567
1568 self.wfile.close()
1569 return True
1570
initial.commit94958cf2008-07-26 22:42:52 +00001571 def DefaultResponseHandler(self):
1572 """This is the catch-all response handler for requests that aren't handled
1573 by one of the special handlers above.
1574 Note that we specify the content-length as without it the https connection
1575 is not closed properly (and the browser keeps expecting data)."""
1576
1577 contents = "Default response given for path: " + self.path
1578 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001579 self.send_header('Content-Type', 'text/html')
1580 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001581 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001582 if (self.command != 'HEAD'):
1583 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001584 return True
1585
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001586 def RedirectConnectHandler(self):
1587 """Sends a redirect to the CONNECT request for www.redirect.com. This
1588 response is not specified by the RFC, so the browser should not follow
1589 the redirect."""
1590
1591 if (self.path.find("www.redirect.com") < 0):
1592 return False
1593
1594 dest = "http://www.destination.com/foo.js"
1595
1596 self.send_response(302) # moved temporarily
1597 self.send_header('Location', dest)
1598 self.send_header('Connection', 'close')
1599 self.end_headers()
1600 return True
1601
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001602 def ServerAuthConnectHandler(self):
1603 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1604 response doesn't make sense because the proxy server cannot request
1605 server authentication."""
1606
1607 if (self.path.find("www.server-auth.com") < 0):
1608 return False
1609
1610 challenge = 'Basic realm="WallyWorld"'
1611
1612 self.send_response(401) # unauthorized
1613 self.send_header('WWW-Authenticate', challenge)
1614 self.send_header('Connection', 'close')
1615 self.end_headers()
1616 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001617
1618 def DefaultConnectResponseHandler(self):
1619 """This is the catch-all response handler for CONNECT requests that aren't
1620 handled by one of the special handlers above. Real Web servers respond
1621 with 400 to CONNECT requests."""
1622
1623 contents = "Your client has issued a malformed or illegal request."
1624 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001625 self.send_header('Content-Type', 'text/html')
1626 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001627 self.end_headers()
1628 self.wfile.write(contents)
1629 return True
1630
initial.commit94958cf2008-07-26 22:42:52 +00001631 # called by the redirect handling function when there is no parameter
1632 def sendRedirectHelp(self, redirect_name):
1633 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001634 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001635 self.end_headers()
1636 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1637 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1638 self.wfile.write('</body></html>')
1639
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001640 # called by chunked handling function
1641 def sendChunkHelp(self, chunk):
1642 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1643 self.wfile.write('%X\r\n' % len(chunk))
1644 self.wfile.write(chunk)
1645 self.wfile.write('\r\n')
1646
akalin@chromium.org154bb132010-11-12 02:20:27 +00001647
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001648class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001649 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001650 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001651 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001652 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001653 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001654 testserver_base.BasePageHandler.__init__(self, request, client_address,
1655 socket_server, [], handlers, [],
1656 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001657
1658 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001659 if self._ShouldHandleRequest("/ocsp"):
1660 response = self.ocsp_response
1661 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1662 response = self.ocsp_response_intermediate
1663 else:
mattm10ede842016-11-29 11:57:16 -08001664 return False
1665 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001666 self.send_response(200)
1667 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001668 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001669 self.end_headers()
1670
Matt Mueller55aef642018-05-02 18:53:57 +00001671 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001672
mattm10ede842016-11-29 11:57:16 -08001673 def CaIssuersResponse(self):
1674 if not self._ShouldHandleRequest("/ca_issuers"):
1675 return False
1676 print 'handling ca_issuers request'
1677 self.send_response(200)
1678 self.send_header('Content-Type', 'application/pkix-cert')
1679 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1680 self.end_headers()
1681
1682 self.wfile.write(self.ca_issuers_response)
1683
mattm@chromium.org830a3712012-11-07 23:00:07 +00001684
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001685class TCPEchoHandler(SocketServer.BaseRequestHandler):
1686 """The RequestHandler class for TCP echo server.
1687
1688 It is instantiated once per connection to the server, and overrides the
1689 handle() method to implement communication to the client.
1690 """
1691
1692 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001693 """Handles the request from the client and constructs a response."""
1694
1695 data = self.request.recv(65536).strip()
1696 # Verify the "echo request" message received from the client. Send back
1697 # "echo response" message if "echo request" message is valid.
1698 try:
1699 return_data = echo_message.GetEchoResponseData(data)
1700 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001701 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001702 except ValueError:
1703 return
1704
1705 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001706
1707
1708class UDPEchoHandler(SocketServer.BaseRequestHandler):
1709 """The RequestHandler class for UDP echo server.
1710
1711 It is instantiated once per connection to the server, and overrides the
1712 handle() method to implement communication to the client.
1713 """
1714
1715 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001716 """Handles the request from the client and constructs a response."""
1717
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001718 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001719 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001720 # Verify the "echo request" message received from the client. Send back
1721 # "echo response" message if "echo request" message is valid.
1722 try:
1723 return_data = echo_message.GetEchoResponseData(data)
1724 if not return_data:
1725 return
1726 except ValueError:
1727 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001728 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001729
1730
Adam Rice9476b8c2018-08-02 15:28:43 +00001731class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1732 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1733 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001734 """
1735
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001736 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001737
bashi@chromium.org33233532012-09-08 17:37:24 +00001738 def _start_read_write(self, sock):
1739 sock.setblocking(0)
1740 self.request.setblocking(0)
1741 rlist = [self.request, sock]
1742 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001743 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001744 if errors:
1745 self.send_response(500)
1746 self.end_headers()
1747 return
1748 for s in ready_sockets:
1749 received = s.recv(1024)
1750 if len(received) == 0:
1751 return
1752 if s == self.request:
1753 other = sock
1754 else:
1755 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001756 # This will lose data if the kernel write buffer fills up.
1757 # TODO(ricea): Correctly use the return value to track how much was
1758 # written and buffer the rest. Use select to determine when the socket
1759 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001760 other.send(received)
1761
1762 def _do_common_method(self):
1763 url = urlparse.urlparse(self.path)
1764 port = url.port
1765 if not port:
1766 if url.scheme == 'http':
1767 port = 80
1768 elif url.scheme == 'https':
1769 port = 443
1770 if not url.hostname or not port:
1771 self.send_response(400)
1772 self.end_headers()
1773 return
1774
1775 if len(url.path) == 0:
1776 path = '/'
1777 else:
1778 path = url.path
1779 if len(url.query) > 0:
1780 path = '%s?%s' % (url.path, url.query)
1781
1782 sock = None
1783 try:
1784 sock = socket.create_connection((url.hostname, port))
1785 sock.send('%s %s %s\r\n' % (
1786 self.command, path, self.protocol_version))
1787 for header in self.headers.headers:
1788 header = header.strip()
1789 if (header.lower().startswith('connection') or
1790 header.lower().startswith('proxy')):
1791 continue
1792 sock.send('%s\r\n' % header)
1793 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001794 # This is wrong: it will pass through connection-level headers and
1795 # misbehave on connection reuse. The only reason it works at all is that
1796 # our test servers have never supported connection reuse.
1797 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001798 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001799 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001800 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001801 self.send_response(500)
1802 self.end_headers()
1803 finally:
1804 if sock is not None:
1805 sock.close()
1806
1807 def do_CONNECT(self):
1808 try:
1809 pos = self.path.rfind(':')
1810 host = self.path[:pos]
1811 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001812 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001813 self.send_response(400)
1814 self.end_headers()
1815
Adam Rice9476b8c2018-08-02 15:28:43 +00001816 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001817 host = "127.0.0.1"
1818
Adam Rice54443aa2018-06-06 00:11:54 +00001819 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001820 try:
1821 sock = socket.create_connection((host, port))
1822 self.send_response(200, 'Connection established')
1823 self.end_headers()
1824 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001825 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001826 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001827 self.send_response(500)
1828 self.end_headers()
1829 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001830 if sock is not None:
1831 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001832
1833 def do_GET(self):
1834 self._do_common_method()
1835
1836 def do_HEAD(self):
1837 self._do_common_method()
1838
Adam Rice9476b8c2018-08-02 15:28:43 +00001839class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1840 """A request handler that behaves as a proxy server which requires
1841 basic authentication.
1842 """
1843
1844 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1845
1846 def parse_request(self):
1847 """Overrides parse_request to check credential."""
1848
1849 if not ProxyRequestHandler.parse_request(self):
1850 return False
1851
1852 auth = self.headers.getheader('Proxy-Authorization')
1853 if auth != self._AUTH_CREDENTIAL:
1854 self.send_response(407)
1855 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1856 self.end_headers()
1857 return False
1858
1859 return True
1860
bashi@chromium.org33233532012-09-08 17:37:24 +00001861
mattm@chromium.org830a3712012-11-07 23:00:07 +00001862class ServerRunner(testserver_base.TestServerRunner):
1863 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001864
mattm@chromium.org830a3712012-11-07 23:00:07 +00001865 def __init__(self):
1866 super(ServerRunner, self).__init__()
1867 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001868
mattm@chromium.org830a3712012-11-07 23:00:07 +00001869 def __make_data_dir(self):
1870 if self.options.data_dir:
1871 if not os.path.isdir(self.options.data_dir):
1872 raise testserver_base.OptionError('specified data dir not found: ' +
1873 self.options.data_dir + ' exiting...')
1874 my_data_dir = self.options.data_dir
1875 else:
1876 # Create the default path to our data dir, relative to the exe dir.
1877 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1878 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001879
mattm@chromium.org830a3712012-11-07 23:00:07 +00001880 #TODO(ibrar): Must use Find* funtion defined in google\tools
1881 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001882
mattm@chromium.org830a3712012-11-07 23:00:07 +00001883 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001884
Matt Mueller55aef642018-05-02 18:53:57 +00001885 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1886 if states_option is None:
1887 return None, None, None
1888
1889 ocsp_states = list()
1890 for ocsp_state_arg in states_option.split(':'):
1891 if ocsp_state_arg == 'ok':
1892 ocsp_state = minica.OCSP_STATE_GOOD
1893 elif ocsp_state_arg == 'revoked':
1894 ocsp_state = minica.OCSP_STATE_REVOKED
1895 elif ocsp_state_arg == 'invalid':
1896 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1897 elif ocsp_state_arg == 'unauthorized':
1898 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1899 elif ocsp_state_arg == 'unknown':
1900 ocsp_state = minica.OCSP_STATE_UNKNOWN
1901 elif ocsp_state_arg == 'later':
1902 ocsp_state = minica.OCSP_STATE_TRY_LATER
1903 elif ocsp_state_arg == 'invalid_data':
1904 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1905 elif ocsp_state_arg == "mismatched_serial":
1906 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1907 else:
1908 raise testserver_base.OptionError('unknown OCSP status: ' +
1909 ocsp_state_arg)
1910 ocsp_states.append(ocsp_state)
1911
1912 if len(ocsp_states) > 1:
1913 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1914 raise testserver_base.OptionError('Multiple OCSP responses '
1915 'incompatible with states ' + str(ocsp_states))
1916
1917 ocsp_dates = list()
1918 for ocsp_date_arg in date_option.split(':'):
1919 if ocsp_date_arg == 'valid':
1920 ocsp_date = minica.OCSP_DATE_VALID
1921 elif ocsp_date_arg == 'old':
1922 ocsp_date = minica.OCSP_DATE_OLD
1923 elif ocsp_date_arg == 'early':
1924 ocsp_date = minica.OCSP_DATE_EARLY
1925 elif ocsp_date_arg == 'long':
1926 ocsp_date = minica.OCSP_DATE_LONG
1927 elif ocsp_date_arg == 'longer':
1928 ocsp_date = minica.OCSP_DATE_LONGER
1929 else:
1930 raise testserver_base.OptionError('unknown OCSP date: ' +
1931 ocsp_date_arg)
1932 ocsp_dates.append(ocsp_date)
1933
1934 if len(ocsp_states) != len(ocsp_dates):
1935 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1936 'count')
1937
1938 ocsp_produced = None
1939 if produced_option == 'valid':
1940 ocsp_produced = minica.OCSP_PRODUCED_VALID
1941 elif produced_option == 'before':
1942 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1943 elif produced_option == 'after':
1944 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1945 else:
1946 raise testserver_base.OptionError('unknown OCSP produced: ' +
1947 produced_option)
1948
1949 return ocsp_states, ocsp_dates, ocsp_produced
1950
mattm@chromium.org830a3712012-11-07 23:00:07 +00001951 def create_server(self, server_data):
1952 port = self.options.port
1953 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001954
Adam Rice54443aa2018-06-06 00:11:54 +00001955 logging.basicConfig()
1956
estark21667d62015-04-08 21:00:16 -07001957 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1958 # will result in a call to |getaddrinfo|, which fails with "nodename
1959 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001960 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001961 if self.options.server_type == SERVER_WEBSOCKET and \
1962 host == "localhost" and \
1963 port == 0:
1964 host = "127.0.0.1"
1965
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001966 # Construct the subjectAltNames for any ad-hoc generated certificates.
1967 # As host can be either a DNS name or IP address, attempt to determine
1968 # which it is, so it can be placed in the appropriate SAN.
1969 dns_sans = None
1970 ip_sans = None
1971 ip = None
1972 try:
1973 ip = socket.inet_aton(host)
1974 ip_sans = [ip]
1975 except socket.error:
1976 pass
1977 if ip is None:
1978 dns_sans = [host]
1979
mattm@chromium.org830a3712012-11-07 23:00:07 +00001980 if self.options.server_type == SERVER_HTTP:
1981 if self.options.https:
1982 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001983 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001984 if self.options.cert_and_key_file:
1985 if not os.path.isfile(self.options.cert_and_key_file):
1986 raise testserver_base.OptionError(
1987 'specified server cert file not found: ' +
1988 self.options.cert_and_key_file + ' exiting...')
1989 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001990 elif self.options.aia_intermediate:
1991 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1992 print ('AIA server started on %s:%d...' %
1993 (host, self.__ocsp_server.server_port))
1994
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001995 ocsp_server_port = self.__ocsp_server.server_port
1996 if self.options.ocsp_proxy_port_number != 0:
1997 ocsp_server_port = self.options.ocsp_proxy_port_number
1998 server_data['ocsp_port'] = self.__ocsp_server.server_port
1999
mattm10ede842016-11-29 11:57:16 -08002000 (pem_cert_and_key, intermediate_cert_der) = \
2001 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00002002 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002003 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002004 ca_issuers_url =
2005 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08002006 serial = self.options.cert_serial)
2007
2008 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00002009 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08002010 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002011 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002012 # generate a new certificate and run an OCSP server for it.
2013 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002014 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00002015 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002016
Matt Mueller55aef642018-05-02 18:53:57 +00002017 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
2018 self.options.ocsp,
2019 self.options.ocsp_date,
2020 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002021
Matt Mueller55aef642018-05-02 18:53:57 +00002022 (ocsp_intermediate_states, ocsp_intermediate_dates,
2023 ocsp_intermediate_produced) = self.__parse_ocsp_options(
2024 self.options.ocsp_intermediate,
2025 self.options.ocsp_intermediate_date,
2026 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002027
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002028 ocsp_server_port = self.__ocsp_server.server_port
2029 if self.options.ocsp_proxy_port_number != 0:
2030 ocsp_server_port = self.options.ocsp_proxy_port_number
2031 server_data['ocsp_port'] = self.__ocsp_server.server_port
2032
Matt Mueller55aef642018-05-02 18:53:57 +00002033 pem_cert_and_key, (ocsp_der,
2034 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00002035 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002036 ip_sans = ip_sans,
2037 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002038 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07002039 ocsp_states = ocsp_states,
2040 ocsp_dates = ocsp_dates,
2041 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00002042 ocsp_intermediate_url = (
2043 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
2044 if ocsp_intermediate_states else None),
2045 ocsp_intermediate_states = ocsp_intermediate_states,
2046 ocsp_intermediate_dates = ocsp_intermediate_dates,
2047 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002048 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002049
davidben3e2564a2014-11-07 18:51:00 -08002050 if self.options.ocsp_server_unavailable:
2051 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00002052 self.__ocsp_server.ocsp_response_intermediate = \
2053 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08002054 else:
2055 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00002056 self.__ocsp_server.ocsp_response_intermediate = \
2057 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08002058 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002059
2060 for ca_cert in self.options.ssl_client_ca:
2061 if not os.path.isfile(ca_cert):
2062 raise testserver_base.OptionError(
2063 'specified trusted client CA file not found: ' + ca_cert +
2064 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002065
2066 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002067 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00002068 # TODO(mattm): Staple the intermediate response too (if applicable,
2069 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08002070 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002071
mattm@chromium.org830a3712012-11-07 23:00:07 +00002072 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2073 self.options.ssl_client_auth,
2074 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002075 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002076 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002077 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002078 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002079 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002080 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002081 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002082 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002083 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002084 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002085 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002086 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002087 self.options.alert_after_handshake,
2088 self.options.disable_channel_id,
David Benjaminf839f1c2018-10-16 06:01:29 +00002089 self.options.disable_extended_master_secret,
2090 self.options.simulate_tls13_downgrade,
2091 self.options.simulate_tls12_downgrade,
2092 self.options.tls_max_version)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002093 print 'HTTPS server started on https://%s:%d...' % \
2094 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 else:
2096 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002097 print 'HTTP server started on http://%s:%d...' % \
2098 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002099
2100 server.data_dir = self.__make_data_dir()
2101 server.file_root_url = self.options.file_root_url
2102 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002103 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002104 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2105 # is required to work correctly. It should be fixed from pywebsocket side.
2106 os.chdir(self.__make_data_dir())
2107 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002108 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002109 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002110 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002111 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002112 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2113 if not os.path.isfile(key_path):
2114 raise testserver_base.OptionError(
2115 'specified server cert file not found: ' +
2116 self.options.cert_and_key_file + ' exiting...')
2117 websocket_options.private_key = key_path
2118 websocket_options.certificate = key_path
2119
mattm@chromium.org830a3712012-11-07 23:00:07 +00002120 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002121 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002122 websocket_options.tls_client_auth = True
2123 if len(self.options.ssl_client_ca) != 1:
2124 raise testserver_base.OptionError(
2125 'one trusted client CA file should be specified')
2126 if not os.path.isfile(self.options.ssl_client_ca[0]):
2127 raise testserver_base.OptionError(
2128 'specified trusted client CA file not found: ' +
2129 self.options.ssl_client_ca[0] + ' exiting...')
2130 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002131 print 'Trying to start websocket server on %s://%s:%d...' % \
2132 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002133 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002134 print 'WebSocket server started on %s://%s:%d...' % \
2135 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002136 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002137 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002138 elif self.options.server_type == SERVER_TCP_ECHO:
2139 # Used for generating the key (randomly) that encodes the "echo request"
2140 # message.
2141 random.seed()
2142 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002143 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002144 server_data['port'] = server.server_port
2145 elif self.options.server_type == SERVER_UDP_ECHO:
2146 # Used for generating the key (randomly) that encodes the "echo request"
2147 # message.
2148 random.seed()
2149 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002150 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002151 server_data['port'] = server.server_port
Adam Rice9476b8c2018-08-02 15:28:43 +00002152 elif self.options.server_type == SERVER_PROXY:
2153 ProxyRequestHandler.redirect_connect_to_localhost = \
2154 self.options.redirect_connect_to_localhost
2155 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
2156 print 'Proxy server started on port %d...' % server.server_port
2157 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002158 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00002159 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002160 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00002161 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002162 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002163 server_data['port'] = server.server_port
2164 elif self.options.server_type == SERVER_FTP:
2165 my_data_dir = self.__make_data_dir()
2166
2167 # Instantiate a dummy authorizer for managing 'virtual' users
2168 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2169
xleng9d4c45f2015-05-04 16:26:12 -07002170 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002171 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2172
xleng9d4c45f2015-05-04 16:26:12 -07002173 # Define a read-only anonymous user unless disabled
2174 if not self.options.no_anonymous_ftp_user:
2175 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002176
2177 # Instantiate FTP handler class
2178 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2179 ftp_handler.authorizer = authorizer
2180
2181 # Define a customized banner (string returned when client connects)
2182 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2183 pyftpdlib.ftpserver.__ver__)
2184
2185 # Instantiate FTP server class and listen to address:port
2186 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2187 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002188 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002189 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002190 raise testserver_base.OptionError('unknown server type' +
2191 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002192
mattm@chromium.org830a3712012-11-07 23:00:07 +00002193 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002194
mattm@chromium.org830a3712012-11-07 23:00:07 +00002195 def run_server(self):
2196 if self.__ocsp_server:
2197 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002198
mattm@chromium.org830a3712012-11-07 23:00:07 +00002199 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002200
mattm@chromium.org830a3712012-11-07 23:00:07 +00002201 if self.__ocsp_server:
2202 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002203
mattm@chromium.org830a3712012-11-07 23:00:07 +00002204 def add_options(self):
2205 testserver_base.TestServerRunner.add_options(self)
2206 self.option_parser.add_option('-f', '--ftp', action='store_const',
2207 const=SERVER_FTP, default=SERVER_HTTP,
2208 dest='server_type',
2209 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002210 self.option_parser.add_option('--tcp-echo', action='store_const',
2211 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2212 dest='server_type',
2213 help='start up a tcp echo server.')
2214 self.option_parser.add_option('--udp-echo', action='store_const',
2215 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2216 dest='server_type',
2217 help='start up a udp echo server.')
Adam Rice9476b8c2018-08-02 15:28:43 +00002218 self.option_parser.add_option('--proxy', action='store_const',
2219 const=SERVER_PROXY,
2220 default=SERVER_HTTP, dest='server_type',
2221 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002222 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2223 const=SERVER_BASIC_AUTH_PROXY,
2224 default=SERVER_HTTP, dest='server_type',
2225 help='start up a proxy server which requires '
2226 'basic authentication.')
2227 self.option_parser.add_option('--websocket', action='store_const',
2228 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2229 dest='server_type',
2230 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002231 self.option_parser.add_option('--https', action='store_true',
2232 dest='https', help='Specify that https '
2233 'should be used.')
2234 self.option_parser.add_option('--cert-and-key-file',
2235 dest='cert_and_key_file', help='specify the '
2236 'path to the file containing the certificate '
2237 'and private key for the server in PEM '
2238 'format')
mattm10ede842016-11-29 11:57:16 -08002239 self.option_parser.add_option('--aia-intermediate', action='store_true',
2240 dest='aia_intermediate',
2241 help='generate a certificate chain that '
2242 'requires AIA cert fetching, and run a '
2243 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002244 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2245 help='The type of OCSP response generated '
2246 'for the automatically generated '
2247 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002248 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2249 default='valid', help='The validity of the '
2250 'range between thisUpdate and nextUpdate')
2251 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2252 default='valid', help='producedAt relative '
2253 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002254 self.option_parser.add_option('--ocsp-intermediate',
2255 dest='ocsp_intermediate', default=None,
2256 help='If specified, the automatically '
2257 'generated chain will include an '
2258 'intermediate certificate with this type '
2259 'of OCSP response (see docs for --ocsp)')
2260 self.option_parser.add_option('--ocsp-intermediate-date',
2261 dest='ocsp_intermediate_date',
2262 default='valid', help='The validity of the '
2263 'range between thisUpdate and nextUpdate')
2264 self.option_parser.add_option('--ocsp-intermediate-produced',
2265 dest='ocsp_intermediate_produced',
2266 default='valid', help='producedAt relative '
2267 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002268 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2269 default=0, type=int,
2270 help='If non-zero then the generated '
2271 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002272 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2273 default="127.0.0.1",
2274 help='The generated certificate will have '
2275 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002276 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2277 default='0', type='int',
2278 help='If nonzero, certain TLS connections '
2279 'will be aborted in order to test version '
2280 'fallback. 1 means all TLS versions will be '
2281 'aborted. 2 means TLS 1.1 or higher will be '
2282 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002283 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002284 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002285 self.option_parser.add_option('--tls-intolerance-type',
2286 dest='tls_intolerance_type',
2287 default="alert",
2288 help='Controls how the server reacts to a '
2289 'TLS version it is intolerant to. Valid '
2290 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002291 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2292 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002293 default='',
2294 help='Base64 encoded SCT list. If set, '
2295 'server will respond with a '
2296 'signed_certificate_timestamp TLS extension '
2297 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002298 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2299 default=False, const=True,
2300 action='store_const',
2301 help='If given, TLS_FALLBACK_SCSV support '
2302 'will be enabled. This causes the server to '
2303 'reject fallback connections from compatible '
2304 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002305 self.option_parser.add_option('--staple-ocsp-response',
2306 dest='staple_ocsp_response',
2307 default=False, action='store_true',
2308 help='If set, server will staple the OCSP '
2309 'response whenever OCSP is on and the client '
2310 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002311 self.option_parser.add_option('--https-record-resume',
2312 dest='record_resume', const=True,
2313 default=False, action='store_const',
2314 help='Record resumption cache events rather '
2315 'than resuming as normal. Allows the use of '
2316 'the /ssl-session-cache request')
2317 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2318 help='Require SSL client auth on every '
2319 'connection.')
2320 self.option_parser.add_option('--ssl-client-ca', action='append',
2321 default=[], help='Specify that the client '
2322 'certificate request should include the CA '
2323 'named in the subject of the DER-encoded '
2324 'certificate contained in the specified '
2325 'file. This option may appear multiple '
2326 'times, indicating multiple CA names should '
2327 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002328 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2329 default=[], help='Specify that the client '
2330 'certificate request should include the '
2331 'specified certificate_type value. This '
2332 'option may appear multiple times, '
2333 'indicating multiple values should be send '
2334 'in the request. Valid values are '
2335 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2336 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002337 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2338 help='Specify the bulk encryption '
2339 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002340 'SSL server. Valid values are "aes128gcm", '
2341 '"aes256", "aes128", "3des", "rc4". If '
2342 'omitted, all algorithms will be used. This '
2343 'option may appear multiple times, '
2344 'indicating multiple algorithms should be '
2345 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002346 self.option_parser.add_option('--ssl-key-exchange', action='append',
2347 help='Specify the key exchange algorithm(s)'
2348 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002349 'Valid values are "rsa", "dhe_rsa", '
2350 '"ecdhe_rsa". If omitted, all algorithms '
2351 'will be used. This option may appear '
2352 'multiple times, indicating multiple '
2353 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002354 self.option_parser.add_option('--alpn-protocols', action='append',
2355 help='Specify the list of ALPN protocols. '
2356 'The server will not send an ALPN response '
2357 'if this list does not overlap with the '
2358 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002359 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002360 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002361 'an NPN response. The server will not'
2362 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002363 self.option_parser.add_option('--file-root-url', default='/files/',
2364 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002365 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2366 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2367 dest='ws_basic_auth',
2368 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002369 self.option_parser.add_option('--ocsp-server-unavailable',
2370 dest='ocsp_server_unavailable',
2371 default=False, action='store_true',
2372 help='If set, the OCSP server will return '
2373 'a tryLater status rather than the actual '
2374 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002375 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2376 type='int', dest='ocsp_proxy_port_number',
2377 help='Port allocated for OCSP proxy '
2378 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002379 self.option_parser.add_option('--alert-after-handshake',
2380 dest='alert_after_handshake',
2381 default=False, action='store_true',
2382 help='If set, the server will send a fatal '
2383 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002384 self.option_parser.add_option('--no-anonymous-ftp-user',
2385 dest='no_anonymous_ftp_user',
2386 default=False, action='store_true',
2387 help='If set, the FTP server will not create '
2388 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002389 self.option_parser.add_option('--disable-channel-id', action='store_true')
2390 self.option_parser.add_option('--disable-extended-master-secret',
2391 action='store_true')
David Benjaminf839f1c2018-10-16 06:01:29 +00002392 self.option_parser.add_option('--simulate-tls13-downgrade',
2393 action='store_true')
2394 self.option_parser.add_option('--simulate-tls12-downgrade',
2395 action='store_true')
2396 self.option_parser.add_option('--tls-max-version', default='0', type='int',
2397 help='If non-zero, the maximum TLS version '
2398 'to support. 1 means TLS 1.0, 2 means '
2399 'TLS 1.1, and 3 means TLS 1.2.')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002400 self.option_parser.add_option('--redirect-connect-to-localhost',
2401 dest='redirect_connect_to_localhost',
2402 default=False, action='store_true',
2403 help='If set, the Proxy server will connect '
2404 'to localhost instead of the requested URL '
2405 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002406
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002407
initial.commit94958cf2008-07-26 22:42:52 +00002408if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002409 sys.exit(ServerRunner().main())