blob: e90b1c1f65e1eb149f17d9eb7092d838583b5155 [file] [log] [blame]
Andrew Grieve9c2b31d2019-03-26 15:08:10 +00001#!/usr/bin/env vpython
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
Adam Rice9476b8c2018-08-02 15:28:43 +00006"""This is a simple HTTP/FTP/TCP/UDP/PROXY/BASIC_AUTH_PROXY/WEBSOCKET server
7used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Insert at the beginning of the path, we want to use our copies of the library
Robert Iannucci0e7ec952018-01-18 22:44:16 +000043# unconditionally (since they contain modifications from anything that might be
44# obtained from e.g. PyPi).
Keita Suzuki83e26f92020-03-06 09:42:48 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket3', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000046sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
47
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000048import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000049from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000050# import manually
51mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000052
davidben@chromium.org7d53b542014-04-10 17:56:44 +000053import pyftpdlib.ftpserver
54
55import tlslite
56import tlslite.api
57
58import echo_message
59import testserver_base
60
maruel@chromium.org756cf982009-03-05 12:46:38 +000061SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000062SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000063SERVER_TCP_ECHO = 2
64SERVER_UDP_ECHO = 3
65SERVER_BASIC_AUTH_PROXY = 4
66SERVER_WEBSOCKET = 5
Adam Rice9476b8c2018-08-02 15:28:43 +000067SERVER_PROXY = 6
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000068
69# Default request queue size for WebSocketServer.
70_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000071
dadrian4ccf51c2016-07-20 15:36:58 -070072OCSP_STATES_NO_SINGLE_RESPONSE = {
73 minica.OCSP_STATE_INVALID_RESPONSE,
74 minica.OCSP_STATE_UNAUTHORIZED,
75 minica.OCSP_STATE_TRY_LATER,
76 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
77}
78
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079class WebSocketOptions:
80 """Holds options for WebSocketServer."""
81
82 def __init__(self, host, port, data_dir):
83 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
84 self.server_host = host
85 self.port = port
86 self.websock_handlers = data_dir
87 self.scan_dir = None
88 self.allow_handlers_outside_root_dir = False
89 self.websock_handlers_map_file = None
90 self.cgi_directories = []
91 self.is_executable_method = None
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000092
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000093 self.use_tls = False
94 self.private_key = None
95 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000096 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000097 self.tls_client_ca = None
98 self.use_basic_auth = False
Keita Suzuki83e26f92020-03-06 09:42:48 +000099 self.basic_auth_credential = 'Basic ' + base64.b64encode(
100 'test:test').decode()
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000101
mattm@chromium.org830a3712012-11-07 23:00:07 +0000102
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000103class RecordingSSLSessionCache(object):
104 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
105 lookups and inserts in order to test session cache behaviours."""
106
107 def __init__(self):
108 self.log = []
109
110 def __getitem__(self, sessionID):
111 self.log.append(('lookup', sessionID))
112 raise KeyError()
113
114 def __setitem__(self, sessionID, session):
115 self.log.append(('insert', sessionID))
116
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000117
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000118class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
119 testserver_base.BrokenPipeHandlerMixIn,
120 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000121 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000122 verification."""
123
124 pass
125
Adam Rice34b2e312018-04-06 16:48:30 +0000126class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
127 HTTPServer):
128 """This variant of HTTPServer creates a new thread for every connection. It
129 should only be used with handlers that are known to be threadsafe."""
130
131 pass
132
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000133class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
134 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000135 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000136 """This is a specialization of HTTPServer that serves an
137 OCSP response"""
138
139 def serve_forever_on_thread(self):
140 self.thread = threading.Thread(target = self.serve_forever,
141 name = "OCSPServerThread")
142 self.thread.start()
143
144 def stop_serving(self):
145 self.shutdown()
146 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000147
mattm@chromium.org830a3712012-11-07 23:00:07 +0000148
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000149class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000150 testserver_base.ClientRestrictingServerMixIn,
151 testserver_base.BrokenPipeHandlerMixIn,
152 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000153 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000154 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000155
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000156 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000157 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700158 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
159 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000160 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700161 fallback_scsv_enabled, ocsp_response,
David Benjaminf839f1c2018-10-16 06:01:29 +0000162 alert_after_handshake, disable_channel_id, disable_ems,
163 simulate_tls13_downgrade, simulate_tls12_downgrade,
164 tls_max_version):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000165 self.cert_chain = tlslite.api.X509CertChain()
166 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000167 # Force using only python implementation - otherwise behavior is different
168 # depending on whether m2crypto Python module is present (error is thrown
169 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
170 # the hood.
171 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
172 private=True,
173 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000174 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000175 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000176 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700177 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000178 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000179 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000180 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000181
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000182 if ssl_client_auth:
183 for ca_file in ssl_client_cas:
184 s = open(ca_file).read()
185 x509 = tlslite.api.X509()
186 x509.parse(s)
187 self.ssl_client_cas.append(x509.subject)
188
189 for cert_type in ssl_client_cert_types:
190 self.ssl_client_cert_types.append({
191 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800196 # Enable SSLv3 for testing purposes.
197 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000202 if tls_intolerant != 0:
203 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
204 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700205 if alert_after_handshake:
206 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700207 if disable_channel_id:
208 self.ssl_handshake_settings.enableChannelID = False
209 if disable_ems:
210 self.ssl_handshake_settings.enableExtendedMasterSecret = False
David Benjaminf839f1c2018-10-16 06:01:29 +0000211 if simulate_tls13_downgrade:
212 self.ssl_handshake_settings.simulateTLS13Downgrade = True
213 if simulate_tls12_downgrade:
214 self.ssl_handshake_settings.simulateTLS12Downgrade = True
215 if tls_max_version != 0:
216 self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
bnc5fb33bd2016-08-05 12:09:21 -0700217 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000218
rsleevi8146efa2015-03-16 12:31:24 -0700219 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000220 # If record_resume_info is true then we'll replace the session cache with
221 # an object that records the lookups and inserts that it sees.
222 self.session_cache = RecordingSSLSessionCache()
223 else:
224 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000225 testserver_base.StoppableHTTPServer.__init__(self,
226 server_address,
227 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000228
229 def handshake(self, tlsConnection):
230 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000231
initial.commit94958cf2008-07-26 22:42:52 +0000232 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000233 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000234 tlsConnection.handshakeServer(certChain=self.cert_chain,
235 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000236 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000237 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000238 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000239 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000240 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700241 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000242 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000243 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000244 fallbackSCSV=self.fallback_scsv_enabled,
245 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000246 tlsConnection.ignoreAbruptClose = True
247 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000248 except tlslite.api.TLSAbruptCloseError:
249 # Ignore abrupt close.
250 return True
initial.commit94958cf2008-07-26 22:42:52 +0000251 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000252 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000253 return False
254
akalin@chromium.org154bb132010-11-12 02:20:27 +0000255
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000256class FTPServer(testserver_base.ClientRestrictingServerMixIn,
257 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000258 """This is a specialization of FTPServer that adds client verification."""
259
260 pass
261
262
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000263class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
264 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000265 """A TCP echo server that echoes back what it has received."""
266
267 def server_bind(self):
268 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000269
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000270 SocketServer.TCPServer.server_bind(self)
271 host, port = self.socket.getsockname()[:2]
272 self.server_name = socket.getfqdn(host)
273 self.server_port = port
274
275 def serve_forever(self):
276 self.stop = False
277 self.nonce_time = None
278 while not self.stop:
279 self.handle_request()
280 self.socket.close()
281
282
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000283class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
284 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000285 """A UDP echo server that echoes back what it has received."""
286
287 def server_bind(self):
288 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000289
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000290 SocketServer.UDPServer.server_bind(self)
291 host, port = self.socket.getsockname()[:2]
292 self.server_name = socket.getfqdn(host)
293 self.server_port = port
294
295 def serve_forever(self):
296 self.stop = False
297 self.nonce_time = None
298 while not self.stop:
299 self.handle_request()
300 self.socket.close()
301
302
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000303class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000304 # Class variables to allow for persistence state between page handler
305 # invocations
306 rst_limits = {}
307 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000308
309 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000310 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000311 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000312 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000313 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000314 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000315 self.NoCacheMaxAgeTimeHandler,
316 self.NoCacheTimeHandler,
317 self.CacheTimeHandler,
318 self.CacheExpiresHandler,
319 self.CacheProxyRevalidateHandler,
320 self.CachePrivateHandler,
321 self.CachePublicHandler,
322 self.CacheSMaxAgeHandler,
323 self.CacheMustRevalidateHandler,
324 self.CacheMustRevalidateMaxAgeHandler,
325 self.CacheNoStoreHandler,
326 self.CacheNoStoreMaxAgeHandler,
327 self.CacheNoTransformHandler,
328 self.DownloadHandler,
329 self.DownloadFinishHandler,
330 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000331 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000332 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000333 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000334 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000335 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000336 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000337 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000338 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000339 self.AuthBasicHandler,
340 self.AuthDigestHandler,
341 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000342 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000343 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000344 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700345 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000346 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000347 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000348 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000349 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700350 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700351 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000352 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000353 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000354 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000355 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000356 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000357 self.PostOnlyFileHandler,
358 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000359 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000360 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000361 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000362 head_handlers = [
363 self.FileHandler,
364 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000365
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000367 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000368 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000369 'gif': 'image/gif',
370 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000371 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700372 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000373 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000374 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000375 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000376 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000377 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000378 }
initial.commit94958cf2008-07-26 22:42:52 +0000379 self._default_mime_type = 'text/html'
380
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000381 testserver_base.BasePageHandler.__init__(self, request, client_address,
382 socket_server, connect_handlers,
383 get_handlers, head_handlers,
384 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000385
initial.commit94958cf2008-07-26 22:42:52 +0000386 def GetMIMETypeFromName(self, file_name):
387 """Returns the mime type for the specified file_name. So far it only looks
388 at the file extension."""
389
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000390 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000391 if len(extension) == 0:
392 # no extension.
393 return self._default_mime_type
394
ericroman@google.comc17ca532009-05-07 03:51:05 +0000395 # extension starts with a dot, so we need to remove it
396 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000397
initial.commit94958cf2008-07-26 22:42:52 +0000398 def NoCacheMaxAgeTimeHandler(self):
399 """This request handler yields a page with the title set to the current
400 system time, and no caching requested."""
401
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000402 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000403 return False
404
405 self.send_response(200)
406 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000407 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000408 self.end_headers()
409
maruel@google.come250a9b2009-03-10 17:39:46 +0000410 self.wfile.write('<html><head><title>%s</title></head></html>' %
411 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000412
413 return True
414
415 def NoCacheTimeHandler(self):
416 """This request handler yields a page with the title set to the current
417 system time, and no caching requested."""
418
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000419 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000420 return False
421
422 self.send_response(200)
423 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000424 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000425 self.end_headers()
426
maruel@google.come250a9b2009-03-10 17:39:46 +0000427 self.wfile.write('<html><head><title>%s</title></head></html>' %
428 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000429
430 return True
431
432 def CacheTimeHandler(self):
433 """This request handler yields a page with the title set to the current
434 system time, and allows caching for one minute."""
435
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000436 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000437 return False
438
439 self.send_response(200)
440 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000441 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000442 self.end_headers()
443
maruel@google.come250a9b2009-03-10 17:39:46 +0000444 self.wfile.write('<html><head><title>%s</title></head></html>' %
445 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000446
447 return True
448
449 def CacheExpiresHandler(self):
450 """This request handler yields a page with the title set to the current
451 system time, and set the page to expire on 1 Jan 2099."""
452
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000453 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000454 return False
455
456 self.send_response(200)
457 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000458 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000459 self.end_headers()
460
maruel@google.come250a9b2009-03-10 17:39:46 +0000461 self.wfile.write('<html><head><title>%s</title></head></html>' %
462 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000463
464 return True
465
466 def CacheProxyRevalidateHandler(self):
467 """This request handler yields a page with the title set to the current
468 system time, and allows caching for 60 seconds"""
469
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000470 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000471 return False
472
473 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000474 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000475 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
476 self.end_headers()
477
maruel@google.come250a9b2009-03-10 17:39:46 +0000478 self.wfile.write('<html><head><title>%s</title></head></html>' %
479 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000480
481 return True
482
483 def CachePrivateHandler(self):
484 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700485 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000486
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000487 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000488 return False
489
490 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000491 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000492 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000493 self.end_headers()
494
maruel@google.come250a9b2009-03-10 17:39:46 +0000495 self.wfile.write('<html><head><title>%s</title></head></html>' %
496 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000497
498 return True
499
500 def CachePublicHandler(self):
501 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700502 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000503
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000504 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000505 return False
506
507 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000508 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000509 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000510 self.end_headers()
511
maruel@google.come250a9b2009-03-10 17:39:46 +0000512 self.wfile.write('<html><head><title>%s</title></head></html>' %
513 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000514
515 return True
516
517 def CacheSMaxAgeHandler(self):
518 """This request handler yields a page with the title set to the current
519 system time, and does not allow for caching."""
520
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000521 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000522 return False
523
524 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000525 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000526 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
527 self.end_headers()
528
maruel@google.come250a9b2009-03-10 17:39:46 +0000529 self.wfile.write('<html><head><title>%s</title></head></html>' %
530 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000531
532 return True
533
534 def CacheMustRevalidateHandler(self):
535 """This request handler yields a page with the title set to the current
536 system time, and does not allow caching."""
537
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000538 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000539 return False
540
541 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000542 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000543 self.send_header('Cache-Control', 'must-revalidate')
544 self.end_headers()
545
maruel@google.come250a9b2009-03-10 17:39:46 +0000546 self.wfile.write('<html><head><title>%s</title></head></html>' %
547 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000548
549 return True
550
551 def CacheMustRevalidateMaxAgeHandler(self):
552 """This request handler yields a page with the title set to the current
553 system time, and does not allow caching event though max-age of 60
554 seconds is specified."""
555
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000556 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000557 return False
558
559 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000560 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000561 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
562 self.end_headers()
563
maruel@google.come250a9b2009-03-10 17:39:46 +0000564 self.wfile.write('<html><head><title>%s</title></head></html>' %
565 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000566
567 return True
568
initial.commit94958cf2008-07-26 22:42:52 +0000569 def CacheNoStoreHandler(self):
570 """This request handler yields a page with the title set to the current
571 system time, and does not allow the page to be stored."""
572
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000573 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000574 return False
575
576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000577 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000578 self.send_header('Cache-Control', 'no-store')
579 self.end_headers()
580
maruel@google.come250a9b2009-03-10 17:39:46 +0000581 self.wfile.write('<html><head><title>%s</title></head></html>' %
582 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000583
584 return True
585
586 def CacheNoStoreMaxAgeHandler(self):
587 """This request handler yields a page with the title set to the current
588 system time, and does not allow the page to be stored even though max-age
589 of 60 seconds is specified."""
590
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000591 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000592 return False
593
594 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000595 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000596 self.send_header('Cache-Control', 'max-age=60, no-store')
597 self.end_headers()
598
maruel@google.come250a9b2009-03-10 17:39:46 +0000599 self.wfile.write('<html><head><title>%s</title></head></html>' %
600 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000601
602 return True
603
604
605 def CacheNoTransformHandler(self):
606 """This request handler yields a page with the title set to the current
607 system time, and does not allow the content to transformed during
608 user-agent caching"""
609
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000610 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000611 return False
612
613 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000614 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000615 self.send_header('Cache-Control', 'no-transform')
616 self.end_headers()
617
maruel@google.come250a9b2009-03-10 17:39:46 +0000618 self.wfile.write('<html><head><title>%s</title></head></html>' %
619 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000620
621 return True
622
623 def EchoHeader(self):
624 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000625
ananta@chromium.org219b2062009-10-23 16:09:41 +0000626 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000627
ananta@chromium.org56812d02011-04-07 17:52:05 +0000628 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000629 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000630 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000631
ananta@chromium.org56812d02011-04-07 17:52:05 +0000632 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000633
634 def EchoHeaderHelper(self, echo_header):
635 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000636
ananta@chromium.org219b2062009-10-23 16:09:41 +0000637 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000638 return False
639
640 query_char = self.path.find('?')
641 if query_char != -1:
642 header_name = self.path[query_char+1:]
643
644 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000645 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000646 if echo_header == '/echoheadercache':
647 self.send_header('Cache-control', 'max-age=60000')
648 else:
649 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000650 # insert a vary header to properly indicate that the cachability of this
651 # request is subject to value of the request header being echoed.
652 if len(header_name) > 0:
653 self.send_header('Vary', header_name)
654 self.end_headers()
655
656 if len(header_name) > 0:
657 self.wfile.write(self.headers.getheader(header_name))
658
659 return True
660
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000661 def ReadRequestBody(self):
662 """This function reads the body of the current HTTP request, handling
663 both plain and chunked transfer encoded requests."""
664
665 if self.headers.getheader('transfer-encoding') != 'chunked':
666 length = int(self.headers.getheader('content-length'))
667 return self.rfile.read(length)
668
669 # Read the request body as chunks.
670 body = ""
671 while True:
672 line = self.rfile.readline()
673 length = int(line, 16)
674 if length == 0:
675 self.rfile.readline()
676 break
677 body += self.rfile.read(length)
678 self.rfile.read(2)
679 return body
680
initial.commit94958cf2008-07-26 22:42:52 +0000681 def EchoHandler(self):
682 """This handler just echoes back the payload of the request, for testing
683 form submission."""
684
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000685 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000686 return False
687
hirono2838c572015-01-21 12:18:11 -0800688 _, _, _, _, query, _ = urlparse.urlparse(self.path)
689 query_params = cgi.parse_qs(query, True)
690 if 'status' in query_params:
691 self.send_response(int(query_params['status'][0]))
692 else:
693 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000694 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000695 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000696 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000697 return True
698
699 def EchoTitleHandler(self):
700 """This handler is like Echo, but sets the page title to the request."""
701
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000702 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000703 return False
704
705 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000706 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000707 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000708 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000709 self.wfile.write('<html><head><title>')
710 self.wfile.write(request)
711 self.wfile.write('</title></head></html>')
712 return True
713
714 def EchoAllHandler(self):
715 """This handler yields a (more) human-readable page listing information
716 about the request header & contents."""
717
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000718 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000719 return False
720
721 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000722 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000723 self.end_headers()
724 self.wfile.write('<html><head><style>'
725 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
726 '</style></head><body>'
727 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000728 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000729 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000730
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000731 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000732 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000733 params = cgi.parse_qs(qs, keep_blank_values=1)
734
735 for param in params:
736 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000737
738 self.wfile.write('</pre>')
739
740 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
741
742 self.wfile.write('</body></html>')
743 return True
744
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000745 def EchoMultipartPostHandler(self):
746 """This handler echoes received multipart post data as json format."""
747
748 if not (self._ShouldHandleRequest("/echomultipartpost") or
749 self._ShouldHandleRequest("/searchbyimage")):
750 return False
751
752 content_type, parameters = cgi.parse_header(
753 self.headers.getheader('content-type'))
754 if content_type == 'multipart/form-data':
755 post_multipart = cgi.parse_multipart(self.rfile, parameters)
756 elif content_type == 'application/x-www-form-urlencoded':
757 raise Exception('POST by application/x-www-form-urlencoded is '
758 'not implemented.')
759 else:
760 post_multipart = {}
761
762 # Since the data can be binary, we encode them by base64.
763 post_multipart_base64_encoded = {}
764 for field, values in post_multipart.items():
765 post_multipart_base64_encoded[field] = [base64.b64encode(value)
766 for value in values]
767
768 result = {'POST_multipart' : post_multipart_base64_encoded}
769
770 self.send_response(200)
771 self.send_header("Content-type", "text/plain")
772 self.end_headers()
773 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
774 return True
775
initial.commit94958cf2008-07-26 22:42:52 +0000776 def DownloadHandler(self):
777 """This handler sends a downloadable file with or without reporting
778 the size (6K)."""
779
780 if self.path.startswith("/download-unknown-size"):
781 send_length = False
782 elif self.path.startswith("/download-known-size"):
783 send_length = True
784 else:
785 return False
786
787 #
788 # The test which uses this functionality is attempting to send
789 # small chunks of data to the client. Use a fairly large buffer
790 # so that we'll fill chrome's IO buffer enough to force it to
791 # actually write the data.
792 # See also the comments in the client-side of this test in
793 # download_uitest.cc
794 #
795 size_chunk1 = 35*1024
796 size_chunk2 = 10*1024
797
798 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000799 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000800 self.send_header('Cache-Control', 'max-age=0')
801 if send_length:
802 self.send_header('Content-Length', size_chunk1 + size_chunk2)
803 self.end_headers()
804
805 # First chunk of data:
806 self.wfile.write("*" * size_chunk1)
807 self.wfile.flush()
808
809 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000810 self.server.wait_for_download = True
811 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000812 self.server.handle_request()
813
814 # Second chunk of data:
815 self.wfile.write("*" * size_chunk2)
816 return True
817
818 def DownloadFinishHandler(self):
819 """This handler just tells the server to finish the current download."""
820
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000821 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000822 return False
823
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000824 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000825 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000826 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000827 self.send_header('Cache-Control', 'max-age=0')
828 self.end_headers()
829 return True
830
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000831 def _ReplaceFileData(self, data, query_parameters):
832 """Replaces matching substrings in a file.
833
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000834 If the 'replace_text' URL query parameter is present, it is expected to be
835 of the form old_text:new_text, which indicates that any old_text strings in
836 the file are replaced with new_text. Multiple 'replace_text' parameters may
837 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000838
839 If the parameters are not present, |data| is returned.
840 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000841
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000842 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000843 replace_text_values = query_dict.get('replace_text', [])
844 for replace_text_value in replace_text_values:
845 replace_text_args = replace_text_value.split(':')
846 if len(replace_text_args) != 2:
847 raise ValueError(
848 'replace_text must be of form old_text:new_text. Actual value: %s' %
849 replace_text_value)
850 old_text_b64, new_text_b64 = replace_text_args
851 old_text = base64.urlsafe_b64decode(old_text_b64)
852 new_text = base64.urlsafe_b64decode(new_text_b64)
853 data = data.replace(old_text, new_text)
854 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000855
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000856 def ZipFileHandler(self):
857 """This handler sends the contents of the requested file in compressed form.
858 Can pass in a parameter that specifies that the content length be
859 C - the compressed size (OK),
860 U - the uncompressed size (Non-standard, but handled),
861 S - less than compressed (OK because we keep going),
862 M - larger than compressed but less than uncompressed (an error),
863 L - larger than uncompressed (an error)
864 Example: compressedfiles/Picture_1.doc?C
865 """
866
867 prefix = "/compressedfiles/"
868 if not self.path.startswith(prefix):
869 return False
870
871 # Consume a request body if present.
872 if self.command == 'POST' or self.command == 'PUT' :
873 self.ReadRequestBody()
874
875 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
876
877 if not query in ('C', 'U', 'S', 'M', 'L'):
878 return False
879
880 sub_path = url_path[len(prefix):]
881 entries = sub_path.split('/')
882 file_path = os.path.join(self.server.data_dir, *entries)
883 if os.path.isdir(file_path):
884 file_path = os.path.join(file_path, 'index.html')
885
886 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000887 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000888 self.send_error(404)
889 return True
890
891 f = open(file_path, "rb")
892 data = f.read()
893 uncompressed_len = len(data)
894 f.close()
895
896 # Compress the data.
897 data = zlib.compress(data)
898 compressed_len = len(data)
899
900 content_length = compressed_len
901 if query == 'U':
902 content_length = uncompressed_len
903 elif query == 'S':
904 content_length = compressed_len / 2
905 elif query == 'M':
906 content_length = (compressed_len + uncompressed_len) / 2
907 elif query == 'L':
908 content_length = compressed_len + uncompressed_len
909
910 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000911 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000912 self.send_header('Content-encoding', 'deflate')
913 self.send_header('Connection', 'close')
914 self.send_header('Content-Length', content_length)
915 self.send_header('ETag', '\'' + file_path + '\'')
916 self.end_headers()
917
918 self.wfile.write(data)
919
920 return True
921
initial.commit94958cf2008-07-26 22:42:52 +0000922 def FileHandler(self):
923 """This handler sends the contents of the requested file. Wow, it's like
924 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000925
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000926 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000927 if not self.path.startswith(prefix):
928 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000929 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000930
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000931 def PostOnlyFileHandler(self):
932 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000933
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000934 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000935 if not self.path.startswith(prefix):
936 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000937 return self._FileHandlerHelper(prefix)
938
939 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000940 request_body = ''
941 if self.command == 'POST' or self.command == 'PUT':
942 # Consume a request body if present.
943 request_body = self.ReadRequestBody()
944
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000945 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000946 query_dict = cgi.parse_qs(query)
947
948 expected_body = query_dict.get('expected_body', [])
949 if expected_body and request_body not in expected_body:
950 self.send_response(404)
951 self.end_headers()
952 self.wfile.write('')
953 return True
954
955 expected_headers = query_dict.get('expected_headers', [])
956 for expected_header in expected_headers:
957 header_name, expected_value = expected_header.split(':')
958 if self.headers.getheader(header_name) != expected_value:
959 self.send_response(404)
960 self.end_headers()
961 self.wfile.write('')
962 return True
963
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000964 sub_path = url_path[len(prefix):]
965 entries = sub_path.split('/')
966 file_path = os.path.join(self.server.data_dir, *entries)
967 if os.path.isdir(file_path):
968 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000969
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000970 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000971 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000972 self.send_error(404)
973 return True
974
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000975 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000976 data = f.read()
977 f.close()
978
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000979 data = self._ReplaceFileData(data, query)
980
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000981 old_protocol_version = self.protocol_version
982
initial.commit94958cf2008-07-26 22:42:52 +0000983 # If file.mock-http-headers exists, it contains the headers we
984 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000985 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000986 if os.path.isfile(headers_path):
987 f = open(headers_path, "r")
988
989 # "HTTP/1.1 200 OK"
990 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000991 http_major, http_minor, status_code = re.findall(
992 'HTTP/(\d+).(\d+) (\d+)', response)[0]
993 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000994 self.send_response(int(status_code))
995
996 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000997 header_values = re.findall('(\S+):\s*(.*)', line)
998 if len(header_values) > 0:
999 # "name: value"
1000 name, value = header_values[0]
1001 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001002 f.close()
1003 else:
1004 # Could be more generic once we support mime-type sniffing, but for
1005 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001006
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001007 range_header = self.headers.get('Range')
1008 if range_header and range_header.startswith('bytes='):
1009 # Note this doesn't handle all valid byte range_header values (i.e.
1010 # left open ended ones), just enough for what we needed so far.
1011 range_header = range_header[6:].split('-')
1012 start = int(range_header[0])
1013 if range_header[1]:
1014 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001015 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001016 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001017
1018 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001019 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1020 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001021 self.send_header('Content-Range', content_range)
1022 data = data[start: end + 1]
1023 else:
1024 self.send_response(200)
1025
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001026 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001027 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001028 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001029 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001030 self.end_headers()
1031
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001032 if (self.command != 'HEAD'):
1033 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001034
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001035 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001036 return True
1037
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001038 def SetCookieHandler(self):
1039 """This handler just sets a cookie, for testing cookie handling."""
1040
1041 if not self._ShouldHandleRequest("/set-cookie"):
1042 return False
1043
1044 query_char = self.path.find('?')
1045 if query_char != -1:
1046 cookie_values = self.path[query_char + 1:].split('&')
1047 else:
1048 cookie_values = ("",)
1049 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001050 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001051 for cookie_value in cookie_values:
1052 self.send_header('Set-Cookie', '%s' % cookie_value)
1053 self.end_headers()
1054 for cookie_value in cookie_values:
1055 self.wfile.write('%s' % cookie_value)
1056 return True
1057
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001058 def SetManyCookiesHandler(self):
1059 """This handler just sets a given number of cookies, for testing handling
1060 of large numbers of cookies."""
1061
1062 if not self._ShouldHandleRequest("/set-many-cookies"):
1063 return False
1064
1065 query_char = self.path.find('?')
1066 if query_char != -1:
1067 num_cookies = int(self.path[query_char + 1:])
1068 else:
1069 num_cookies = 0
1070 self.send_response(200)
1071 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001072 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001073 self.send_header('Set-Cookie', 'a=')
1074 self.end_headers()
1075 self.wfile.write('%d cookies were sent' % num_cookies)
1076 return True
1077
mattm@chromium.org983fc462012-06-30 00:52:08 +00001078 def ExpectAndSetCookieHandler(self):
1079 """Expects some cookies to be sent, and if they are, sets more cookies.
1080
1081 The expect parameter specifies a required cookie. May be specified multiple
1082 times.
1083 The set parameter specifies a cookie to set if all required cookies are
1084 preset. May be specified multiple times.
1085 The data parameter specifies the response body data to be returned."""
1086
1087 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1088 return False
1089
1090 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1091 query_dict = cgi.parse_qs(query)
1092 cookies = set()
1093 if 'Cookie' in self.headers:
1094 cookie_header = self.headers.getheader('Cookie')
1095 cookies.update([s.strip() for s in cookie_header.split(';')])
1096 got_all_expected_cookies = True
1097 for expected_cookie in query_dict.get('expect', []):
1098 if expected_cookie not in cookies:
1099 got_all_expected_cookies = False
1100 self.send_response(200)
1101 self.send_header('Content-Type', 'text/html')
1102 if got_all_expected_cookies:
1103 for cookie_value in query_dict.get('set', []):
1104 self.send_header('Set-Cookie', '%s' % cookie_value)
1105 self.end_headers()
1106 for data_value in query_dict.get('data', []):
1107 self.wfile.write(data_value)
1108 return True
1109
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001110 def SetHeaderHandler(self):
1111 """This handler sets a response header. Parameters are in the
1112 key%3A%20value&key2%3A%20value2 format."""
1113
1114 if not self._ShouldHandleRequest("/set-header"):
1115 return False
1116
1117 query_char = self.path.find('?')
1118 if query_char != -1:
1119 headers_values = self.path[query_char + 1:].split('&')
1120 else:
1121 headers_values = ("",)
1122 self.send_response(200)
1123 self.send_header('Content-Type', 'text/html')
1124 for header_value in headers_values:
1125 header_value = urllib.unquote(header_value)
1126 (key, value) = header_value.split(': ', 1)
1127 self.send_header(key, value)
1128 self.end_headers()
1129 for header_value in headers_values:
1130 self.wfile.write('%s' % header_value)
1131 return True
1132
initial.commit94958cf2008-07-26 22:42:52 +00001133 def AuthBasicHandler(self):
1134 """This handler tests 'Basic' authentication. It just sends a page with
1135 title 'user/pass' if you succeed."""
1136
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001137 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001138 return False
1139
1140 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001141 expected_password = 'secret'
1142 realm = 'testrealm'
1143 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001144
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001145 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1146 query_params = cgi.parse_qs(query, True)
1147 if 'set-cookie-if-challenged' in query_params:
1148 set_cookie_if_challenged = True
1149 if 'password' in query_params:
1150 expected_password = query_params['password'][0]
1151 if 'realm' in query_params:
1152 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001153
initial.commit94958cf2008-07-26 22:42:52 +00001154 auth = self.headers.getheader('authorization')
1155 try:
1156 if not auth:
1157 raise Exception('no auth')
1158 b64str = re.findall(r'Basic (\S+)', auth)[0]
1159 userpass = base64.b64decode(b64str)
1160 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001161 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001162 raise Exception('wrong password')
1163 except Exception, e:
1164 # Authentication failed.
1165 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001166 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001167 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001168 if set_cookie_if_challenged:
1169 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001170 self.end_headers()
1171 self.wfile.write('<html><head>')
1172 self.wfile.write('<title>Denied: %s</title>' % e)
1173 self.wfile.write('</head><body>')
1174 self.wfile.write('auth=%s<p>' % auth)
1175 self.wfile.write('b64str=%s<p>' % b64str)
1176 self.wfile.write('username: %s<p>' % username)
1177 self.wfile.write('userpass: %s<p>' % userpass)
1178 self.wfile.write('password: %s<p>' % password)
1179 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1180 self.wfile.write('</body></html>')
1181 return True
1182
1183 # Authentication successful. (Return a cachable response to allow for
1184 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001185 old_protocol_version = self.protocol_version
1186 self.protocol_version = "HTTP/1.1"
1187
initial.commit94958cf2008-07-26 22:42:52 +00001188 if_none_match = self.headers.getheader('if-none-match')
1189 if if_none_match == "abc":
1190 self.send_response(304)
1191 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001192 elif url_path.endswith(".gif"):
1193 # Using chrome/test/data/google/logo.gif as the test image
1194 test_image_path = ['google', 'logo.gif']
1195 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1196 if not os.path.isfile(gif_path):
1197 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001198 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001199 return True
1200
1201 f = open(gif_path, "rb")
1202 data = f.read()
1203 f.close()
1204
1205 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001206 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001207 self.send_header('Cache-control', 'max-age=60000')
1208 self.send_header('Etag', 'abc')
1209 self.end_headers()
1210 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001211 else:
1212 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001213 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001214 self.send_header('Cache-control', 'max-age=60000')
1215 self.send_header('Etag', 'abc')
1216 self.end_headers()
1217 self.wfile.write('<html><head>')
1218 self.wfile.write('<title>%s/%s</title>' % (username, password))
1219 self.wfile.write('</head><body>')
1220 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001221 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001222 self.wfile.write('</body></html>')
1223
rvargas@google.com54453b72011-05-19 01:11:11 +00001224 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001225 return True
1226
tonyg@chromium.org75054202010-03-31 22:06:10 +00001227 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001228 """Returns a nonce that's stable per request path for the server's lifetime.
1229 This is a fake implementation. A real implementation would only use a given
1230 nonce a single time (hence the name n-once). However, for the purposes of
1231 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001232
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001233 Args:
1234 force_reset: Iff set, the nonce will be changed. Useful for testing the
1235 "stale" response.
1236 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001237
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001238 if force_reset or not self.server.nonce_time:
1239 self.server.nonce_time = time.time()
1240 return hashlib.md5('privatekey%s%d' %
1241 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001242
1243 def AuthDigestHandler(self):
1244 """This handler tests 'Digest' authentication.
1245
1246 It just sends a page with title 'user/pass' if you succeed.
1247
1248 A stale response is sent iff "stale" is present in the request path.
1249 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001250
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001251 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001252 return False
1253
tonyg@chromium.org75054202010-03-31 22:06:10 +00001254 stale = 'stale' in self.path
1255 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001256 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001257 password = 'secret'
1258 realm = 'testrealm'
1259
1260 auth = self.headers.getheader('authorization')
1261 pairs = {}
1262 try:
1263 if not auth:
1264 raise Exception('no auth')
1265 if not auth.startswith('Digest'):
1266 raise Exception('not digest')
1267 # Pull out all the name="value" pairs as a dictionary.
1268 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1269
1270 # Make sure it's all valid.
1271 if pairs['nonce'] != nonce:
1272 raise Exception('wrong nonce')
1273 if pairs['opaque'] != opaque:
1274 raise Exception('wrong opaque')
1275
1276 # Check the 'response' value and make sure it matches our magic hash.
1277 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001278 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001279 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001280 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001281 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001282 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001283 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1284 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001285 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001286
1287 if pairs['response'] != response:
1288 raise Exception('wrong password')
1289 except Exception, e:
1290 # Authentication failed.
1291 self.send_response(401)
1292 hdr = ('Digest '
1293 'realm="%s", '
1294 'domain="/", '
1295 'qop="auth", '
1296 'algorithm=MD5, '
1297 'nonce="%s", '
1298 'opaque="%s"') % (realm, nonce, opaque)
1299 if stale:
1300 hdr += ', stale="TRUE"'
1301 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001302 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001303 self.end_headers()
1304 self.wfile.write('<html><head>')
1305 self.wfile.write('<title>Denied: %s</title>' % e)
1306 self.wfile.write('</head><body>')
1307 self.wfile.write('auth=%s<p>' % auth)
1308 self.wfile.write('pairs=%s<p>' % pairs)
1309 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1310 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1311 self.wfile.write('</body></html>')
1312 return True
1313
1314 # Authentication successful.
1315 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001316 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001317 self.end_headers()
1318 self.wfile.write('<html><head>')
1319 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1320 self.wfile.write('</head><body>')
1321 self.wfile.write('auth=%s<p>' % auth)
1322 self.wfile.write('pairs=%s<p>' % pairs)
1323 self.wfile.write('</body></html>')
1324
1325 return True
1326
1327 def SlowServerHandler(self):
1328 """Wait for the user suggested time before responding. The syntax is
1329 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001330
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001331 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001332 return False
1333 query_char = self.path.find('?')
1334 wait_sec = 1.0
1335 if query_char >= 0:
1336 try:
davidben05f82202015-03-31 13:48:07 -07001337 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001338 except ValueError:
1339 pass
1340 time.sleep(wait_sec)
1341 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001342 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001343 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001344 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001345 return True
1346
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001347 def ChunkedServerHandler(self):
1348 """Send chunked response. Allows to specify chunks parameters:
1349 - waitBeforeHeaders - ms to wait before sending headers
1350 - waitBetweenChunks - ms to wait between chunks
1351 - chunkSize - size of each chunk in bytes
1352 - chunksNumber - number of chunks
1353 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1354 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001355
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001356 if not self._ShouldHandleRequest("/chunked"):
1357 return False
1358 query_char = self.path.find('?')
1359 chunkedSettings = {'waitBeforeHeaders' : 0,
1360 'waitBetweenChunks' : 0,
1361 'chunkSize' : 5,
1362 'chunksNumber' : 5}
1363 if query_char >= 0:
1364 params = self.path[query_char + 1:].split('&')
1365 for param in params:
1366 keyValue = param.split('=')
1367 if len(keyValue) == 2:
1368 try:
1369 chunkedSettings[keyValue[0]] = int(keyValue[1])
1370 except ValueError:
1371 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001372 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001373 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1374 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001375 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001376 self.send_header('Connection', 'close')
1377 self.send_header('Transfer-Encoding', 'chunked')
1378 self.end_headers()
1379 # Chunked encoding: sending all chunks, then final zero-length chunk and
1380 # then final CRLF.
1381 for i in range(0, chunkedSettings['chunksNumber']):
1382 if i > 0:
1383 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1384 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001385 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001386 self.sendChunkHelp('')
1387 return True
1388
creis@google.com2f4f6a42011-03-25 19:44:19 +00001389 def NoContentHandler(self):
1390 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001391
creis@google.com2f4f6a42011-03-25 19:44:19 +00001392 if not self._ShouldHandleRequest("/nocontent"):
1393 return False
1394 self.send_response(204)
1395 self.end_headers()
1396 return True
1397
initial.commit94958cf2008-07-26 22:42:52 +00001398 def ServerRedirectHandler(self):
1399 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001400 '/server-redirect?http://foo.bar/asdf' to redirect to
1401 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001402
1403 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001404 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001405 return False
1406
1407 query_char = self.path.find('?')
1408 if query_char < 0 or len(self.path) <= query_char + 1:
1409 self.sendRedirectHelp(test_name)
1410 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001411 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001412
1413 self.send_response(301) # moved permanently
1414 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001416 self.end_headers()
1417 self.wfile.write('<html><head>')
1418 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1419
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001420 return True
initial.commit94958cf2008-07-26 22:42:52 +00001421
naskoe7a0d0d2014-09-29 08:53:05 -07001422 def CrossSiteRedirectHandler(self):
1423 """Sends a server redirect to the given site. The syntax is
1424 '/cross-site/hostname/...' to redirect to //hostname/...
1425 It is used to navigate between different Sites, causing
1426 cross-site/cross-process navigations in the browser."""
1427
1428 test_name = "/cross-site"
1429 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001430 return False
1431
1432 params = urllib.unquote(self.path[(len(test_name) + 1):])
1433 slash = params.find('/')
1434 if slash < 0:
1435 self.sendRedirectHelp(test_name)
1436 return True
1437
1438 host = params[:slash]
1439 path = params[(slash+1):]
1440 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1441
1442 self.send_response(301) # moved permanently
1443 self.send_header('Location', dest)
1444 self.send_header('Content-Type', 'text/html')
1445 self.end_headers()
1446 self.wfile.write('<html><head>')
1447 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1448
1449 return True
1450
initial.commit94958cf2008-07-26 22:42:52 +00001451 def ClientRedirectHandler(self):
1452 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001453 '/client-redirect?http://foo.bar/asdf' to redirect to
1454 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001455
1456 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001457 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001458 return False
1459
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001460 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001461 if query_char < 0 or len(self.path) <= query_char + 1:
1462 self.sendRedirectHelp(test_name)
1463 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001464 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001465
1466 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001467 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001468 self.end_headers()
1469 self.wfile.write('<html><head>')
1470 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1471 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1472
1473 return True
1474
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001475 def GetSSLSessionCacheHandler(self):
1476 """Send a reply containing a log of the session cache operations."""
1477
1478 if not self._ShouldHandleRequest('/ssl-session-cache'):
1479 return False
1480
1481 self.send_response(200)
1482 self.send_header('Content-Type', 'text/plain')
1483 self.end_headers()
1484 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001485 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001486 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001487 self.wfile.write('Pass --https-record-resume in order to use' +
1488 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001489 return True
1490
1491 for (action, sessionID) in log:
1492 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001493 return True
1494
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001495 def SSLManySmallRecords(self):
1496 """Sends a reply consisting of a variety of small writes. These will be
1497 translated into a series of small SSL records when used over an HTTPS
1498 server."""
1499
1500 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1501 return False
1502
1503 self.send_response(200)
1504 self.send_header('Content-Type', 'text/plain')
1505 self.end_headers()
1506
1507 # Write ~26K of data, in 1350 byte chunks
1508 for i in xrange(20):
1509 self.wfile.write('*' * 1350)
1510 self.wfile.flush()
1511 return True
1512
agl@chromium.org04700be2013-03-02 18:40:41 +00001513 def GetChannelID(self):
1514 """Send a reply containing the hashed ChannelID that the client provided."""
1515
1516 if not self._ShouldHandleRequest('/channel-id'):
1517 return False
1518
1519 self.send_response(200)
1520 self.send_header('Content-Type', 'text/plain')
1521 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001522 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001523 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1524 return True
1525
pneubeckfd4f0442015-08-07 04:55:10 -07001526 def GetClientCert(self):
1527 """Send a reply whether a client certificate was provided."""
1528
1529 if not self._ShouldHandleRequest('/client-cert'):
1530 return False
1531
1532 self.send_response(200)
1533 self.send_header('Content-Type', 'text/plain')
1534 self.end_headers()
1535
1536 cert_chain = self.server.tlsConnection.session.clientCertChain
1537 if cert_chain != None:
1538 self.wfile.write('got client cert with fingerprint: ' +
1539 cert_chain.getFingerprint())
1540 else:
1541 self.wfile.write('got no client cert')
1542 return True
1543
davidben599e7e72014-09-03 16:19:09 -07001544 def ClientCipherListHandler(self):
1545 """Send a reply containing the cipher suite list that the client
1546 provided. Each cipher suite value is serialized in decimal, followed by a
1547 newline."""
1548
1549 if not self._ShouldHandleRequest('/client-cipher-list'):
1550 return False
1551
1552 self.send_response(200)
1553 self.send_header('Content-Type', 'text/plain')
1554 self.end_headers()
1555
davidben11682512014-10-06 21:09:11 -07001556 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1557 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001558 return True
1559
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001560 def CloseSocketHandler(self):
1561 """Closes the socket without sending anything."""
1562
1563 if not self._ShouldHandleRequest('/close-socket'):
1564 return False
1565
1566 self.wfile.close()
1567 return True
1568
initial.commit94958cf2008-07-26 22:42:52 +00001569 def DefaultResponseHandler(self):
1570 """This is the catch-all response handler for requests that aren't handled
1571 by one of the special handlers above.
1572 Note that we specify the content-length as without it the https connection
1573 is not closed properly (and the browser keeps expecting data)."""
1574
1575 contents = "Default response given for path: " + self.path
1576 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001577 self.send_header('Content-Type', 'text/html')
1578 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001579 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001580 if (self.command != 'HEAD'):
1581 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001582 return True
1583
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001584 def RedirectConnectHandler(self):
1585 """Sends a redirect to the CONNECT request for www.redirect.com. This
1586 response is not specified by the RFC, so the browser should not follow
1587 the redirect."""
1588
1589 if (self.path.find("www.redirect.com") < 0):
1590 return False
1591
1592 dest = "http://www.destination.com/foo.js"
1593
1594 self.send_response(302) # moved temporarily
1595 self.send_header('Location', dest)
1596 self.send_header('Connection', 'close')
1597 self.end_headers()
1598 return True
1599
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001600 def ServerAuthConnectHandler(self):
1601 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1602 response doesn't make sense because the proxy server cannot request
1603 server authentication."""
1604
1605 if (self.path.find("www.server-auth.com") < 0):
1606 return False
1607
1608 challenge = 'Basic realm="WallyWorld"'
1609
1610 self.send_response(401) # unauthorized
1611 self.send_header('WWW-Authenticate', challenge)
1612 self.send_header('Connection', 'close')
1613 self.end_headers()
1614 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001615
1616 def DefaultConnectResponseHandler(self):
1617 """This is the catch-all response handler for CONNECT requests that aren't
1618 handled by one of the special handlers above. Real Web servers respond
1619 with 400 to CONNECT requests."""
1620
1621 contents = "Your client has issued a malformed or illegal request."
1622 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001623 self.send_header('Content-Type', 'text/html')
1624 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001625 self.end_headers()
1626 self.wfile.write(contents)
1627 return True
1628
initial.commit94958cf2008-07-26 22:42:52 +00001629 # called by the redirect handling function when there is no parameter
1630 def sendRedirectHelp(self, redirect_name):
1631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001632 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001633 self.end_headers()
1634 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1635 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1636 self.wfile.write('</body></html>')
1637
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001638 # called by chunked handling function
1639 def sendChunkHelp(self, chunk):
1640 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1641 self.wfile.write('%X\r\n' % len(chunk))
1642 self.wfile.write(chunk)
1643 self.wfile.write('\r\n')
1644
akalin@chromium.org154bb132010-11-12 02:20:27 +00001645
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001646class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001647 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001648 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001649 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001650 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001651 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001652 testserver_base.BasePageHandler.__init__(self, request, client_address,
1653 socket_server, [], handlers, [],
1654 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001655
1656 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001657 if self._ShouldHandleRequest("/ocsp"):
1658 response = self.ocsp_response
1659 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1660 response = self.ocsp_response_intermediate
1661 else:
mattm10ede842016-11-29 11:57:16 -08001662 return False
1663 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001664 self.send_response(200)
1665 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001666 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001667 self.end_headers()
1668
Matt Mueller55aef642018-05-02 18:53:57 +00001669 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001670
mattm10ede842016-11-29 11:57:16 -08001671 def CaIssuersResponse(self):
1672 if not self._ShouldHandleRequest("/ca_issuers"):
1673 return False
1674 print 'handling ca_issuers request'
1675 self.send_response(200)
1676 self.send_header('Content-Type', 'application/pkix-cert')
1677 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1678 self.end_headers()
1679
1680 self.wfile.write(self.ca_issuers_response)
1681
mattm@chromium.org830a3712012-11-07 23:00:07 +00001682
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001683class TCPEchoHandler(SocketServer.BaseRequestHandler):
1684 """The RequestHandler class for TCP echo server.
1685
1686 It is instantiated once per connection to the server, and overrides the
1687 handle() method to implement communication to the client.
1688 """
1689
1690 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001691 """Handles the request from the client and constructs a response."""
1692
1693 data = self.request.recv(65536).strip()
1694 # Verify the "echo request" message received from the client. Send back
1695 # "echo response" message if "echo request" message is valid.
1696 try:
1697 return_data = echo_message.GetEchoResponseData(data)
1698 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001699 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001700 except ValueError:
1701 return
1702
1703 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001704
1705
1706class UDPEchoHandler(SocketServer.BaseRequestHandler):
1707 """The RequestHandler class for UDP echo server.
1708
1709 It is instantiated once per connection to the server, and overrides the
1710 handle() method to implement communication to the client.
1711 """
1712
1713 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001714 """Handles the request from the client and constructs a response."""
1715
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001716 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001717 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001718 # Verify the "echo request" message received from the client. Send back
1719 # "echo response" message if "echo request" message is valid.
1720 try:
1721 return_data = echo_message.GetEchoResponseData(data)
1722 if not return_data:
1723 return
1724 except ValueError:
1725 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001726 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001727
1728
Adam Rice9476b8c2018-08-02 15:28:43 +00001729class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1730 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1731 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001732 """
1733
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001734 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001735
bashi@chromium.org33233532012-09-08 17:37:24 +00001736 def _start_read_write(self, sock):
1737 sock.setblocking(0)
1738 self.request.setblocking(0)
1739 rlist = [self.request, sock]
1740 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001741 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001742 if errors:
1743 self.send_response(500)
1744 self.end_headers()
1745 return
1746 for s in ready_sockets:
1747 received = s.recv(1024)
1748 if len(received) == 0:
1749 return
1750 if s == self.request:
1751 other = sock
1752 else:
1753 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001754 # This will lose data if the kernel write buffer fills up.
1755 # TODO(ricea): Correctly use the return value to track how much was
1756 # written and buffer the rest. Use select to determine when the socket
1757 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001758 other.send(received)
1759
1760 def _do_common_method(self):
1761 url = urlparse.urlparse(self.path)
1762 port = url.port
1763 if not port:
1764 if url.scheme == 'http':
1765 port = 80
1766 elif url.scheme == 'https':
1767 port = 443
1768 if not url.hostname or not port:
1769 self.send_response(400)
1770 self.end_headers()
1771 return
1772
1773 if len(url.path) == 0:
1774 path = '/'
1775 else:
1776 path = url.path
1777 if len(url.query) > 0:
1778 path = '%s?%s' % (url.path, url.query)
1779
1780 sock = None
1781 try:
1782 sock = socket.create_connection((url.hostname, port))
1783 sock.send('%s %s %s\r\n' % (
1784 self.command, path, self.protocol_version))
1785 for header in self.headers.headers:
1786 header = header.strip()
1787 if (header.lower().startswith('connection') or
1788 header.lower().startswith('proxy')):
1789 continue
1790 sock.send('%s\r\n' % header)
1791 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001792 # This is wrong: it will pass through connection-level headers and
1793 # misbehave on connection reuse. The only reason it works at all is that
1794 # our test servers have never supported connection reuse.
1795 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001796 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001797 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001798 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001799 self.send_response(500)
1800 self.end_headers()
1801 finally:
1802 if sock is not None:
1803 sock.close()
1804
1805 def do_CONNECT(self):
1806 try:
1807 pos = self.path.rfind(':')
1808 host = self.path[:pos]
1809 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001810 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001811 self.send_response(400)
1812 self.end_headers()
1813
Adam Rice9476b8c2018-08-02 15:28:43 +00001814 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001815 host = "127.0.0.1"
1816
Adam Rice54443aa2018-06-06 00:11:54 +00001817 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001818 try:
1819 sock = socket.create_connection((host, port))
1820 self.send_response(200, 'Connection established')
1821 self.end_headers()
1822 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001823 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001824 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001825 self.send_response(500)
1826 self.end_headers()
1827 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001828 if sock is not None:
1829 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001830
1831 def do_GET(self):
1832 self._do_common_method()
1833
1834 def do_HEAD(self):
1835 self._do_common_method()
1836
Adam Rice9476b8c2018-08-02 15:28:43 +00001837class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1838 """A request handler that behaves as a proxy server which requires
1839 basic authentication.
1840 """
1841
1842 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1843
1844 def parse_request(self):
1845 """Overrides parse_request to check credential."""
1846
1847 if not ProxyRequestHandler.parse_request(self):
1848 return False
1849
1850 auth = self.headers.getheader('Proxy-Authorization')
1851 if auth != self._AUTH_CREDENTIAL:
1852 self.send_response(407)
1853 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1854 self.end_headers()
1855 return False
1856
1857 return True
1858
bashi@chromium.org33233532012-09-08 17:37:24 +00001859
mattm@chromium.org830a3712012-11-07 23:00:07 +00001860class ServerRunner(testserver_base.TestServerRunner):
1861 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001862
mattm@chromium.org830a3712012-11-07 23:00:07 +00001863 def __init__(self):
1864 super(ServerRunner, self).__init__()
1865 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001866
mattm@chromium.org830a3712012-11-07 23:00:07 +00001867 def __make_data_dir(self):
1868 if self.options.data_dir:
1869 if not os.path.isdir(self.options.data_dir):
1870 raise testserver_base.OptionError('specified data dir not found: ' +
1871 self.options.data_dir + ' exiting...')
1872 my_data_dir = self.options.data_dir
1873 else:
1874 # Create the default path to our data dir, relative to the exe dir.
Asanka Herath0ec37152019-08-02 15:23:57 +00001875 my_data_dir = os.path.join(BASE_DIR, "..", "..", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001876
mattm@chromium.org830a3712012-11-07 23:00:07 +00001877 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001878
Matt Mueller55aef642018-05-02 18:53:57 +00001879 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1880 if states_option is None:
1881 return None, None, None
1882
1883 ocsp_states = list()
1884 for ocsp_state_arg in states_option.split(':'):
1885 if ocsp_state_arg == 'ok':
1886 ocsp_state = minica.OCSP_STATE_GOOD
1887 elif ocsp_state_arg == 'revoked':
1888 ocsp_state = minica.OCSP_STATE_REVOKED
1889 elif ocsp_state_arg == 'invalid':
1890 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1891 elif ocsp_state_arg == 'unauthorized':
1892 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1893 elif ocsp_state_arg == 'unknown':
1894 ocsp_state = minica.OCSP_STATE_UNKNOWN
1895 elif ocsp_state_arg == 'later':
1896 ocsp_state = minica.OCSP_STATE_TRY_LATER
1897 elif ocsp_state_arg == 'invalid_data':
1898 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1899 elif ocsp_state_arg == "mismatched_serial":
1900 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1901 else:
1902 raise testserver_base.OptionError('unknown OCSP status: ' +
1903 ocsp_state_arg)
1904 ocsp_states.append(ocsp_state)
1905
1906 if len(ocsp_states) > 1:
1907 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1908 raise testserver_base.OptionError('Multiple OCSP responses '
1909 'incompatible with states ' + str(ocsp_states))
1910
1911 ocsp_dates = list()
1912 for ocsp_date_arg in date_option.split(':'):
1913 if ocsp_date_arg == 'valid':
1914 ocsp_date = minica.OCSP_DATE_VALID
1915 elif ocsp_date_arg == 'old':
1916 ocsp_date = minica.OCSP_DATE_OLD
1917 elif ocsp_date_arg == 'early':
1918 ocsp_date = minica.OCSP_DATE_EARLY
1919 elif ocsp_date_arg == 'long':
1920 ocsp_date = minica.OCSP_DATE_LONG
1921 elif ocsp_date_arg == 'longer':
1922 ocsp_date = minica.OCSP_DATE_LONGER
1923 else:
1924 raise testserver_base.OptionError('unknown OCSP date: ' +
1925 ocsp_date_arg)
1926 ocsp_dates.append(ocsp_date)
1927
1928 if len(ocsp_states) != len(ocsp_dates):
1929 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1930 'count')
1931
1932 ocsp_produced = None
1933 if produced_option == 'valid':
1934 ocsp_produced = minica.OCSP_PRODUCED_VALID
1935 elif produced_option == 'before':
1936 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1937 elif produced_option == 'after':
1938 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1939 else:
1940 raise testserver_base.OptionError('unknown OCSP produced: ' +
1941 produced_option)
1942
1943 return ocsp_states, ocsp_dates, ocsp_produced
1944
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 def create_server(self, server_data):
1946 port = self.options.port
1947 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001948
Adam Rice54443aa2018-06-06 00:11:54 +00001949 logging.basicConfig()
1950
estark21667d62015-04-08 21:00:16 -07001951 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1952 # will result in a call to |getaddrinfo|, which fails with "nodename
1953 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001954 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001955 if self.options.server_type == SERVER_WEBSOCKET and \
1956 host == "localhost" and \
1957 port == 0:
1958 host = "127.0.0.1"
1959
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001960 # Construct the subjectAltNames for any ad-hoc generated certificates.
1961 # As host can be either a DNS name or IP address, attempt to determine
1962 # which it is, so it can be placed in the appropriate SAN.
1963 dns_sans = None
1964 ip_sans = None
1965 ip = None
1966 try:
1967 ip = socket.inet_aton(host)
1968 ip_sans = [ip]
1969 except socket.error:
1970 pass
1971 if ip is None:
1972 dns_sans = [host]
1973
mattm@chromium.org830a3712012-11-07 23:00:07 +00001974 if self.options.server_type == SERVER_HTTP:
1975 if self.options.https:
1976 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001977 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001978 if self.options.cert_and_key_file:
1979 if not os.path.isfile(self.options.cert_and_key_file):
1980 raise testserver_base.OptionError(
1981 'specified server cert file not found: ' +
1982 self.options.cert_and_key_file + ' exiting...')
1983 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001984 elif self.options.aia_intermediate:
1985 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1986 print ('AIA server started on %s:%d...' %
1987 (host, self.__ocsp_server.server_port))
1988
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001989 ocsp_server_port = self.__ocsp_server.server_port
1990 if self.options.ocsp_proxy_port_number != 0:
1991 ocsp_server_port = self.options.ocsp_proxy_port_number
1992 server_data['ocsp_port'] = self.__ocsp_server.server_port
1993
mattm10ede842016-11-29 11:57:16 -08001994 (pem_cert_and_key, intermediate_cert_der) = \
1995 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001996 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001997 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001998 ca_issuers_url =
1999 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08002000 serial = self.options.cert_serial)
2001
2002 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00002003 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08002004 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002005 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002006 # generate a new certificate and run an OCSP server for it.
2007 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002008 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002010
Matt Mueller55aef642018-05-02 18:53:57 +00002011 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
2012 self.options.ocsp,
2013 self.options.ocsp_date,
2014 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002015
Matt Mueller55aef642018-05-02 18:53:57 +00002016 (ocsp_intermediate_states, ocsp_intermediate_dates,
2017 ocsp_intermediate_produced) = self.__parse_ocsp_options(
2018 self.options.ocsp_intermediate,
2019 self.options.ocsp_intermediate_date,
2020 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002021
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002022 ocsp_server_port = self.__ocsp_server.server_port
2023 if self.options.ocsp_proxy_port_number != 0:
2024 ocsp_server_port = self.options.ocsp_proxy_port_number
2025 server_data['ocsp_port'] = self.__ocsp_server.server_port
2026
Matt Mueller55aef642018-05-02 18:53:57 +00002027 pem_cert_and_key, (ocsp_der,
2028 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00002029 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002030 ip_sans = ip_sans,
2031 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002032 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07002033 ocsp_states = ocsp_states,
2034 ocsp_dates = ocsp_dates,
2035 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00002036 ocsp_intermediate_url = (
2037 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
2038 if ocsp_intermediate_states else None),
2039 ocsp_intermediate_states = ocsp_intermediate_states,
2040 ocsp_intermediate_dates = ocsp_intermediate_dates,
2041 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002042 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002043
davidben3e2564a2014-11-07 18:51:00 -08002044 if self.options.ocsp_server_unavailable:
2045 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00002046 self.__ocsp_server.ocsp_response_intermediate = \
2047 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08002048 else:
2049 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00002050 self.__ocsp_server.ocsp_response_intermediate = \
2051 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08002052 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053
2054 for ca_cert in self.options.ssl_client_ca:
2055 if not os.path.isfile(ca_cert):
2056 raise testserver_base.OptionError(
2057 'specified trusted client CA file not found: ' + ca_cert +
2058 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002059
2060 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002061 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00002062 # TODO(mattm): Staple the intermediate response too (if applicable,
2063 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08002064 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002065
mattm@chromium.org830a3712012-11-07 23:00:07 +00002066 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2067 self.options.ssl_client_auth,
2068 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002069 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002071 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002072 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002073 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002074 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002075 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002076 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002077 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002078 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002079 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002080 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002081 self.options.alert_after_handshake,
2082 self.options.disable_channel_id,
David Benjaminf839f1c2018-10-16 06:01:29 +00002083 self.options.disable_extended_master_secret,
2084 self.options.simulate_tls13_downgrade,
2085 self.options.simulate_tls12_downgrade,
2086 self.options.tls_max_version)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002087 print 'HTTPS server started on https://%s:%d...' % \
2088 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002089 else:
2090 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002091 print 'HTTP server started on http://%s:%d...' % \
2092 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002093
2094 server.data_dir = self.__make_data_dir()
2095 server.file_root_url = self.options.file_root_url
2096 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002097 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002098 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2099 # is required to work correctly. It should be fixed from pywebsocket side.
2100 os.chdir(self.__make_data_dir())
2101 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002102 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002103 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002104 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002105 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002106 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2107 if not os.path.isfile(key_path):
2108 raise testserver_base.OptionError(
2109 'specified server cert file not found: ' +
2110 self.options.cert_and_key_file + ' exiting...')
2111 websocket_options.private_key = key_path
2112 websocket_options.certificate = key_path
2113
mattm@chromium.org830a3712012-11-07 23:00:07 +00002114 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002115 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002116 websocket_options.tls_client_auth = True
2117 if len(self.options.ssl_client_ca) != 1:
2118 raise testserver_base.OptionError(
2119 'one trusted client CA file should be specified')
2120 if not os.path.isfile(self.options.ssl_client_ca[0]):
2121 raise testserver_base.OptionError(
2122 'specified trusted client CA file not found: ' +
2123 self.options.ssl_client_ca[0] + ' exiting...')
2124 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002125 print 'Trying to start websocket server on %s://%s:%d...' % \
2126 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002127 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002128 print 'WebSocket server started on %s://%s:%d...' % \
2129 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002131 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002132 elif self.options.server_type == SERVER_TCP_ECHO:
2133 # Used for generating the key (randomly) that encodes the "echo request"
2134 # message.
2135 random.seed()
2136 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002137 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002138 server_data['port'] = server.server_port
2139 elif self.options.server_type == SERVER_UDP_ECHO:
2140 # Used for generating the key (randomly) that encodes the "echo request"
2141 # message.
2142 random.seed()
2143 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002144 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002145 server_data['port'] = server.server_port
Adam Rice9476b8c2018-08-02 15:28:43 +00002146 elif self.options.server_type == SERVER_PROXY:
2147 ProxyRequestHandler.redirect_connect_to_localhost = \
2148 self.options.redirect_connect_to_localhost
2149 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
2150 print 'Proxy server started on port %d...' % server.server_port
2151 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002152 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00002153 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002154 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00002155 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002156 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002157 server_data['port'] = server.server_port
2158 elif self.options.server_type == SERVER_FTP:
2159 my_data_dir = self.__make_data_dir()
2160
2161 # Instantiate a dummy authorizer for managing 'virtual' users
2162 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2163
xleng9d4c45f2015-05-04 16:26:12 -07002164 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002165 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2166
xleng9d4c45f2015-05-04 16:26:12 -07002167 # Define a read-only anonymous user unless disabled
2168 if not self.options.no_anonymous_ftp_user:
2169 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002170
2171 # Instantiate FTP handler class
2172 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2173 ftp_handler.authorizer = authorizer
2174
2175 # Define a customized banner (string returned when client connects)
2176 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2177 pyftpdlib.ftpserver.__ver__)
2178
2179 # Instantiate FTP server class and listen to address:port
2180 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2181 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002182 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002183 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002184 raise testserver_base.OptionError('unknown server type' +
2185 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002186
mattm@chromium.org830a3712012-11-07 23:00:07 +00002187 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002188
mattm@chromium.org830a3712012-11-07 23:00:07 +00002189 def run_server(self):
2190 if self.__ocsp_server:
2191 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002192
mattm@chromium.org830a3712012-11-07 23:00:07 +00002193 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002194
mattm@chromium.org830a3712012-11-07 23:00:07 +00002195 if self.__ocsp_server:
2196 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002197
mattm@chromium.org830a3712012-11-07 23:00:07 +00002198 def add_options(self):
2199 testserver_base.TestServerRunner.add_options(self)
2200 self.option_parser.add_option('-f', '--ftp', action='store_const',
2201 const=SERVER_FTP, default=SERVER_HTTP,
2202 dest='server_type',
2203 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002204 self.option_parser.add_option('--tcp-echo', action='store_const',
2205 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2206 dest='server_type',
2207 help='start up a tcp echo server.')
2208 self.option_parser.add_option('--udp-echo', action='store_const',
2209 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2210 dest='server_type',
2211 help='start up a udp echo server.')
Adam Rice9476b8c2018-08-02 15:28:43 +00002212 self.option_parser.add_option('--proxy', action='store_const',
2213 const=SERVER_PROXY,
2214 default=SERVER_HTTP, dest='server_type',
2215 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002216 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2217 const=SERVER_BASIC_AUTH_PROXY,
2218 default=SERVER_HTTP, dest='server_type',
2219 help='start up a proxy server which requires '
2220 'basic authentication.')
2221 self.option_parser.add_option('--websocket', action='store_const',
2222 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2223 dest='server_type',
2224 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002225 self.option_parser.add_option('--https', action='store_true',
2226 dest='https', help='Specify that https '
2227 'should be used.')
2228 self.option_parser.add_option('--cert-and-key-file',
2229 dest='cert_and_key_file', help='specify the '
2230 'path to the file containing the certificate '
2231 'and private key for the server in PEM '
2232 'format')
mattm10ede842016-11-29 11:57:16 -08002233 self.option_parser.add_option('--aia-intermediate', action='store_true',
2234 dest='aia_intermediate',
2235 help='generate a certificate chain that '
2236 'requires AIA cert fetching, and run a '
2237 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002238 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2239 help='The type of OCSP response generated '
2240 'for the automatically generated '
2241 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002242 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2243 default='valid', help='The validity of the '
2244 'range between thisUpdate and nextUpdate')
2245 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2246 default='valid', help='producedAt relative '
2247 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002248 self.option_parser.add_option('--ocsp-intermediate',
2249 dest='ocsp_intermediate', default=None,
2250 help='If specified, the automatically '
2251 'generated chain will include an '
2252 'intermediate certificate with this type '
2253 'of OCSP response (see docs for --ocsp)')
2254 self.option_parser.add_option('--ocsp-intermediate-date',
2255 dest='ocsp_intermediate_date',
2256 default='valid', help='The validity of the '
2257 'range between thisUpdate and nextUpdate')
2258 self.option_parser.add_option('--ocsp-intermediate-produced',
2259 dest='ocsp_intermediate_produced',
2260 default='valid', help='producedAt relative '
2261 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002262 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2263 default=0, type=int,
2264 help='If non-zero then the generated '
2265 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002266 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2267 default="127.0.0.1",
2268 help='The generated certificate will have '
2269 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002270 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2271 default='0', type='int',
2272 help='If nonzero, certain TLS connections '
2273 'will be aborted in order to test version '
2274 'fallback. 1 means all TLS versions will be '
2275 'aborted. 2 means TLS 1.1 or higher will be '
2276 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002277 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002278 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002279 self.option_parser.add_option('--tls-intolerance-type',
2280 dest='tls_intolerance_type',
2281 default="alert",
2282 help='Controls how the server reacts to a '
2283 'TLS version it is intolerant to. Valid '
2284 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002285 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2286 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002287 default='',
2288 help='Base64 encoded SCT list. If set, '
2289 'server will respond with a '
2290 'signed_certificate_timestamp TLS extension '
2291 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002292 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2293 default=False, const=True,
2294 action='store_const',
2295 help='If given, TLS_FALLBACK_SCSV support '
2296 'will be enabled. This causes the server to '
2297 'reject fallback connections from compatible '
2298 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002299 self.option_parser.add_option('--staple-ocsp-response',
2300 dest='staple_ocsp_response',
2301 default=False, action='store_true',
2302 help='If set, server will staple the OCSP '
2303 'response whenever OCSP is on and the client '
2304 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002305 self.option_parser.add_option('--https-record-resume',
2306 dest='record_resume', const=True,
2307 default=False, action='store_const',
2308 help='Record resumption cache events rather '
2309 'than resuming as normal. Allows the use of '
2310 'the /ssl-session-cache request')
2311 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2312 help='Require SSL client auth on every '
2313 'connection.')
2314 self.option_parser.add_option('--ssl-client-ca', action='append',
2315 default=[], help='Specify that the client '
2316 'certificate request should include the CA '
2317 'named in the subject of the DER-encoded '
2318 'certificate contained in the specified '
2319 'file. This option may appear multiple '
2320 'times, indicating multiple CA names should '
2321 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002322 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2323 default=[], help='Specify that the client '
2324 'certificate request should include the '
2325 'specified certificate_type value. This '
2326 'option may appear multiple times, '
2327 'indicating multiple values should be send '
2328 'in the request. Valid values are '
2329 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2330 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002331 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2332 help='Specify the bulk encryption '
2333 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002334 'SSL server. Valid values are "aes128gcm", '
2335 '"aes256", "aes128", "3des", "rc4". If '
2336 'omitted, all algorithms will be used. This '
2337 'option may appear multiple times, '
2338 'indicating multiple algorithms should be '
2339 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002340 self.option_parser.add_option('--ssl-key-exchange', action='append',
2341 help='Specify the key exchange algorithm(s)'
2342 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002343 'Valid values are "rsa", "dhe_rsa", '
2344 '"ecdhe_rsa". If omitted, all algorithms '
2345 'will be used. This option may appear '
2346 'multiple times, indicating multiple '
2347 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002348 self.option_parser.add_option('--alpn-protocols', action='append',
2349 help='Specify the list of ALPN protocols. '
2350 'The server will not send an ALPN response '
2351 'if this list does not overlap with the '
2352 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002353 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002354 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002355 'an NPN response. The server will not'
2356 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002357 self.option_parser.add_option('--file-root-url', default='/files/',
2358 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002359 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2360 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2361 dest='ws_basic_auth',
2362 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002363 self.option_parser.add_option('--ocsp-server-unavailable',
2364 dest='ocsp_server_unavailable',
2365 default=False, action='store_true',
2366 help='If set, the OCSP server will return '
2367 'a tryLater status rather than the actual '
2368 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002369 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2370 type='int', dest='ocsp_proxy_port_number',
2371 help='Port allocated for OCSP proxy '
2372 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002373 self.option_parser.add_option('--alert-after-handshake',
2374 dest='alert_after_handshake',
2375 default=False, action='store_true',
2376 help='If set, the server will send a fatal '
2377 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002378 self.option_parser.add_option('--no-anonymous-ftp-user',
2379 dest='no_anonymous_ftp_user',
2380 default=False, action='store_true',
2381 help='If set, the FTP server will not create '
2382 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002383 self.option_parser.add_option('--disable-channel-id', action='store_true')
2384 self.option_parser.add_option('--disable-extended-master-secret',
2385 action='store_true')
David Benjaminf839f1c2018-10-16 06:01:29 +00002386 self.option_parser.add_option('--simulate-tls13-downgrade',
2387 action='store_true')
2388 self.option_parser.add_option('--simulate-tls12-downgrade',
2389 action='store_true')
2390 self.option_parser.add_option('--tls-max-version', default='0', type='int',
2391 help='If non-zero, the maximum TLS version '
2392 'to support. 1 means TLS 1.0, 2 means '
2393 'TLS 1.1, and 3 means TLS 1.2.')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002394 self.option_parser.add_option('--redirect-connect-to-localhost',
2395 dest='redirect_connect_to_localhost',
2396 default=False, action='store_true',
2397 help='If set, the Proxy server will connect '
2398 'to localhost instead of the requested URL '
2399 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002400
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002401
initial.commit94958cf2008-07-26 22:42:52 +00002402if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002403 sys.exit(ServerRunner().main())