blob: 4ec483109e60173ea7f387cde78b63cb0c62db68 [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Insert at the beginning of the path, we want to use our copies of the library
Robert Iannucci0e7ec952018-01-18 22:44:16 +000043# unconditionally (since they contain modifications from anything that might be
44# obtained from e.g. PyPi).
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000046sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
47
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000048import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000049from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000050# import manually
51mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000052
davidben@chromium.org7d53b542014-04-10 17:56:44 +000053import pyftpdlib.ftpserver
54
55import tlslite
56import tlslite.api
57
58import echo_message
59import testserver_base
60
maruel@chromium.org756cf982009-03-05 12:46:38 +000061SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000062SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000063SERVER_TCP_ECHO = 2
64SERVER_UDP_ECHO = 3
65SERVER_BASIC_AUTH_PROXY = 4
66SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000067
68# Default request queue size for WebSocketServer.
69_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000070
dadrian4ccf51c2016-07-20 15:36:58 -070071OCSP_STATES_NO_SINGLE_RESPONSE = {
72 minica.OCSP_STATE_INVALID_RESPONSE,
73 minica.OCSP_STATE_UNAUTHORIZED,
74 minica.OCSP_STATE_TRY_LATER,
75 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
76}
77
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000078class WebSocketOptions:
79 """Holds options for WebSocketServer."""
80
81 def __init__(self, host, port, data_dir):
82 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
83 self.server_host = host
84 self.port = port
85 self.websock_handlers = data_dir
86 self.scan_dir = None
87 self.allow_handlers_outside_root_dir = False
88 self.websock_handlers_map_file = None
89 self.cgi_directories = []
90 self.is_executable_method = None
91 self.allow_draft75 = False
92 self.strict = True
93
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000094 self.use_tls = False
95 self.private_key = None
96 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000097 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000098 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000099 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000100 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000101 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000102
mattm@chromium.org830a3712012-11-07 23:00:07 +0000103
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000104class RecordingSSLSessionCache(object):
105 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
106 lookups and inserts in order to test session cache behaviours."""
107
108 def __init__(self):
109 self.log = []
110
111 def __getitem__(self, sessionID):
112 self.log.append(('lookup', sessionID))
113 raise KeyError()
114
115 def __setitem__(self, sessionID, session):
116 self.log.append(('insert', sessionID))
117
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000118
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000119class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
120 testserver_base.BrokenPipeHandlerMixIn,
121 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000122 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123 verification."""
124
125 pass
126
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000127class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
128 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000129 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000130 """This is a specialization of HTTPServer that serves an
131 OCSP response"""
132
133 def serve_forever_on_thread(self):
134 self.thread = threading.Thread(target = self.serve_forever,
135 name = "OCSPServerThread")
136 self.thread.start()
137
138 def stop_serving(self):
139 self.shutdown()
140 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000141
mattm@chromium.org830a3712012-11-07 23:00:07 +0000142
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000143class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000144 testserver_base.ClientRestrictingServerMixIn,
145 testserver_base.BrokenPipeHandlerMixIn,
146 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000147 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000149
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000150 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000151 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700152 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
153 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000154 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700155 fallback_scsv_enabled, ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -0700156 alert_after_handshake, disable_channel_id, disable_ems,
157 token_binding_params):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000158 self.cert_chain = tlslite.api.X509CertChain()
159 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000160 # Force using only python implementation - otherwise behavior is different
161 # depending on whether m2crypto Python module is present (error is thrown
162 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
163 # the hood.
164 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
165 private=True,
166 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000167 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000168 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000169 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700170 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000171 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000172 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000173 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000174
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000175 if ssl_client_auth:
176 for ca_file in ssl_client_cas:
177 s = open(ca_file).read()
178 x509 = tlslite.api.X509()
179 x509.parse(s)
180 self.ssl_client_cas.append(x509.subject)
181
182 for cert_type in ssl_client_cert_types:
183 self.ssl_client_cert_types.append({
184 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000185 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
186 }[cert_type])
187
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000188 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800189 # Enable SSLv3 for testing purposes.
190 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000191 if ssl_bulk_ciphers is not None:
192 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000193 if ssl_key_exchanges is not None:
194 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000195 if tls_intolerant != 0:
196 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
197 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700198 if alert_after_handshake:
199 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700200 if disable_channel_id:
201 self.ssl_handshake_settings.enableChannelID = False
202 if disable_ems:
203 self.ssl_handshake_settings.enableExtendedMasterSecret = False
204 self.ssl_handshake_settings.supportedTokenBindingParams = \
205 token_binding_params
bnc5fb33bd2016-08-05 12:09:21 -0700206 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000207
rsleevi8146efa2015-03-16 12:31:24 -0700208 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000209 # If record_resume_info is true then we'll replace the session cache with
210 # an object that records the lookups and inserts that it sees.
211 self.session_cache = RecordingSSLSessionCache()
212 else:
213 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000214 testserver_base.StoppableHTTPServer.__init__(self,
215 server_address,
216 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000217
218 def handshake(self, tlsConnection):
219 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000220
initial.commit94958cf2008-07-26 22:42:52 +0000221 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000222 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000223 tlsConnection.handshakeServer(certChain=self.cert_chain,
224 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000225 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000226 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000227 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000228 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000229 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700230 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000231 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000232 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000233 fallbackSCSV=self.fallback_scsv_enabled,
234 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000235 tlsConnection.ignoreAbruptClose = True
236 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000237 except tlslite.api.TLSAbruptCloseError:
238 # Ignore abrupt close.
239 return True
initial.commit94958cf2008-07-26 22:42:52 +0000240 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000241 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000242 return False
243
akalin@chromium.org154bb132010-11-12 02:20:27 +0000244
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000245class FTPServer(testserver_base.ClientRestrictingServerMixIn,
246 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000247 """This is a specialization of FTPServer that adds client verification."""
248
249 pass
250
251
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000252class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
253 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000254 """A TCP echo server that echoes back what it has received."""
255
256 def server_bind(self):
257 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000258
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000259 SocketServer.TCPServer.server_bind(self)
260 host, port = self.socket.getsockname()[:2]
261 self.server_name = socket.getfqdn(host)
262 self.server_port = port
263
264 def serve_forever(self):
265 self.stop = False
266 self.nonce_time = None
267 while not self.stop:
268 self.handle_request()
269 self.socket.close()
270
271
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000272class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
273 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000274 """A UDP echo server that echoes back what it has received."""
275
276 def server_bind(self):
277 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000278
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000279 SocketServer.UDPServer.server_bind(self)
280 host, port = self.socket.getsockname()[:2]
281 self.server_name = socket.getfqdn(host)
282 self.server_port = port
283
284 def serve_forever(self):
285 self.stop = False
286 self.nonce_time = None
287 while not self.stop:
288 self.handle_request()
289 self.socket.close()
290
291
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000292class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000293 # Class variables to allow for persistence state between page handler
294 # invocations
295 rst_limits = {}
296 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000297
298 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000299 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000300 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000301 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000302 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000304 self.NoCacheMaxAgeTimeHandler,
305 self.NoCacheTimeHandler,
306 self.CacheTimeHandler,
307 self.CacheExpiresHandler,
308 self.CacheProxyRevalidateHandler,
309 self.CachePrivateHandler,
310 self.CachePublicHandler,
311 self.CacheSMaxAgeHandler,
312 self.CacheMustRevalidateHandler,
313 self.CacheMustRevalidateMaxAgeHandler,
314 self.CacheNoStoreHandler,
315 self.CacheNoStoreMaxAgeHandler,
316 self.CacheNoTransformHandler,
317 self.DownloadHandler,
318 self.DownloadFinishHandler,
319 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000320 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000321 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000322 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000323 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000324 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000325 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000326 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000327 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000328 self.AuthBasicHandler,
329 self.AuthDigestHandler,
330 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000331 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000332 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700334 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000336 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000337 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000338 self.GetChannelID,
nharper08eae822016-01-25 15:54:14 -0800339 self.GetTokenBindingEKM,
nharpercb1adc32016-03-30 16:05:48 -0700340 self.ForwardTokenBindingHeader,
pneubeckfd4f0442015-08-07 04:55:10 -0700341 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700342 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000343 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000344 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000345 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000346 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000347 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000348 self.PostOnlyFileHandler,
349 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000350 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000351 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000353 head_handlers = [
354 self.FileHandler,
355 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000356
maruel@google.come250a9b2009-03-10 17:39:46 +0000357 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000358 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000359 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000360 'gif': 'image/gif',
361 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000362 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700363 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000364 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000365 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000366 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000367 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000368 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000369 }
initial.commit94958cf2008-07-26 22:42:52 +0000370 self._default_mime_type = 'text/html'
371
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000372 testserver_base.BasePageHandler.__init__(self, request, client_address,
373 socket_server, connect_handlers,
374 get_handlers, head_handlers,
375 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000376
initial.commit94958cf2008-07-26 22:42:52 +0000377 def GetMIMETypeFromName(self, file_name):
378 """Returns the mime type for the specified file_name. So far it only looks
379 at the file extension."""
380
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000381 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000382 if len(extension) == 0:
383 # no extension.
384 return self._default_mime_type
385
ericroman@google.comc17ca532009-05-07 03:51:05 +0000386 # extension starts with a dot, so we need to remove it
387 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000388
initial.commit94958cf2008-07-26 22:42:52 +0000389 def NoCacheMaxAgeTimeHandler(self):
390 """This request handler yields a page with the title set to the current
391 system time, and no caching requested."""
392
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000393 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000394 return False
395
396 self.send_response(200)
397 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000398 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000399 self.end_headers()
400
maruel@google.come250a9b2009-03-10 17:39:46 +0000401 self.wfile.write('<html><head><title>%s</title></head></html>' %
402 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000403
404 return True
405
406 def NoCacheTimeHandler(self):
407 """This request handler yields a page with the title set to the current
408 system time, and no caching requested."""
409
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000410 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000411 return False
412
413 self.send_response(200)
414 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000416 self.end_headers()
417
maruel@google.come250a9b2009-03-10 17:39:46 +0000418 self.wfile.write('<html><head><title>%s</title></head></html>' %
419 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000420
421 return True
422
423 def CacheTimeHandler(self):
424 """This request handler yields a page with the title set to the current
425 system time, and allows caching for one minute."""
426
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000427 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000428 return False
429
430 self.send_response(200)
431 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000432 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000433 self.end_headers()
434
maruel@google.come250a9b2009-03-10 17:39:46 +0000435 self.wfile.write('<html><head><title>%s</title></head></html>' %
436 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000437
438 return True
439
440 def CacheExpiresHandler(self):
441 """This request handler yields a page with the title set to the current
442 system time, and set the page to expire on 1 Jan 2099."""
443
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000444 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000445 return False
446
447 self.send_response(200)
448 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000449 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000450 self.end_headers()
451
maruel@google.come250a9b2009-03-10 17:39:46 +0000452 self.wfile.write('<html><head><title>%s</title></head></html>' %
453 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000454
455 return True
456
457 def CacheProxyRevalidateHandler(self):
458 """This request handler yields a page with the title set to the current
459 system time, and allows caching for 60 seconds"""
460
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000461 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000462 return False
463
464 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000465 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000466 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
467 self.end_headers()
468
maruel@google.come250a9b2009-03-10 17:39:46 +0000469 self.wfile.write('<html><head><title>%s</title></head></html>' %
470 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000471
472 return True
473
474 def CachePrivateHandler(self):
475 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700476 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000477
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000478 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000479 return False
480
481 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000482 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000483 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000484 self.end_headers()
485
maruel@google.come250a9b2009-03-10 17:39:46 +0000486 self.wfile.write('<html><head><title>%s</title></head></html>' %
487 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000488
489 return True
490
491 def CachePublicHandler(self):
492 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700493 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000494
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000495 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000496 return False
497
498 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000499 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000500 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000501 self.end_headers()
502
maruel@google.come250a9b2009-03-10 17:39:46 +0000503 self.wfile.write('<html><head><title>%s</title></head></html>' %
504 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000505
506 return True
507
508 def CacheSMaxAgeHandler(self):
509 """This request handler yields a page with the title set to the current
510 system time, and does not allow for caching."""
511
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000512 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000513 return False
514
515 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000516 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000517 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
518 self.end_headers()
519
maruel@google.come250a9b2009-03-10 17:39:46 +0000520 self.wfile.write('<html><head><title>%s</title></head></html>' %
521 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000522
523 return True
524
525 def CacheMustRevalidateHandler(self):
526 """This request handler yields a page with the title set to the current
527 system time, and does not allow caching."""
528
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000529 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000530 return False
531
532 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000533 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000534 self.send_header('Cache-Control', 'must-revalidate')
535 self.end_headers()
536
maruel@google.come250a9b2009-03-10 17:39:46 +0000537 self.wfile.write('<html><head><title>%s</title></head></html>' %
538 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000539
540 return True
541
542 def CacheMustRevalidateMaxAgeHandler(self):
543 """This request handler yields a page with the title set to the current
544 system time, and does not allow caching event though max-age of 60
545 seconds is specified."""
546
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000547 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000548 return False
549
550 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000551 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000552 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
553 self.end_headers()
554
maruel@google.come250a9b2009-03-10 17:39:46 +0000555 self.wfile.write('<html><head><title>%s</title></head></html>' %
556 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000557
558 return True
559
initial.commit94958cf2008-07-26 22:42:52 +0000560 def CacheNoStoreHandler(self):
561 """This request handler yields a page with the title set to the current
562 system time, and does not allow the page to be stored."""
563
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000564 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000565 return False
566
567 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000568 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000569 self.send_header('Cache-Control', 'no-store')
570 self.end_headers()
571
maruel@google.come250a9b2009-03-10 17:39:46 +0000572 self.wfile.write('<html><head><title>%s</title></head></html>' %
573 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000574
575 return True
576
577 def CacheNoStoreMaxAgeHandler(self):
578 """This request handler yields a page with the title set to the current
579 system time, and does not allow the page to be stored even though max-age
580 of 60 seconds is specified."""
581
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000582 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000583 return False
584
585 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000586 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000587 self.send_header('Cache-Control', 'max-age=60, no-store')
588 self.end_headers()
589
maruel@google.come250a9b2009-03-10 17:39:46 +0000590 self.wfile.write('<html><head><title>%s</title></head></html>' %
591 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000592
593 return True
594
595
596 def CacheNoTransformHandler(self):
597 """This request handler yields a page with the title set to the current
598 system time, and does not allow the content to transformed during
599 user-agent caching"""
600
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000601 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000602 return False
603
604 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000605 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000606 self.send_header('Cache-Control', 'no-transform')
607 self.end_headers()
608
maruel@google.come250a9b2009-03-10 17:39:46 +0000609 self.wfile.write('<html><head><title>%s</title></head></html>' %
610 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000611
612 return True
613
614 def EchoHeader(self):
615 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000616
ananta@chromium.org219b2062009-10-23 16:09:41 +0000617 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000618
ananta@chromium.org56812d02011-04-07 17:52:05 +0000619 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000620 """This function echoes back the value of a specific request header while
621 allowing caching for 16 hours."""
622
ananta@chromium.org56812d02011-04-07 17:52:05 +0000623 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000624
625 def EchoHeaderHelper(self, echo_header):
626 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000627
ananta@chromium.org219b2062009-10-23 16:09:41 +0000628 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000629 return False
630
631 query_char = self.path.find('?')
632 if query_char != -1:
633 header_name = self.path[query_char+1:]
634
635 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000636 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000637 if echo_header == '/echoheadercache':
638 self.send_header('Cache-control', 'max-age=60000')
639 else:
640 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000641 # insert a vary header to properly indicate that the cachability of this
642 # request is subject to value of the request header being echoed.
643 if len(header_name) > 0:
644 self.send_header('Vary', header_name)
645 self.end_headers()
646
647 if len(header_name) > 0:
648 self.wfile.write(self.headers.getheader(header_name))
649
650 return True
651
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000652 def ReadRequestBody(self):
653 """This function reads the body of the current HTTP request, handling
654 both plain and chunked transfer encoded requests."""
655
656 if self.headers.getheader('transfer-encoding') != 'chunked':
657 length = int(self.headers.getheader('content-length'))
658 return self.rfile.read(length)
659
660 # Read the request body as chunks.
661 body = ""
662 while True:
663 line = self.rfile.readline()
664 length = int(line, 16)
665 if length == 0:
666 self.rfile.readline()
667 break
668 body += self.rfile.read(length)
669 self.rfile.read(2)
670 return body
671
initial.commit94958cf2008-07-26 22:42:52 +0000672 def EchoHandler(self):
673 """This handler just echoes back the payload of the request, for testing
674 form submission."""
675
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000676 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000677 return False
678
hirono2838c572015-01-21 12:18:11 -0800679 _, _, _, _, query, _ = urlparse.urlparse(self.path)
680 query_params = cgi.parse_qs(query, True)
681 if 'status' in query_params:
682 self.send_response(int(query_params['status'][0]))
683 else:
684 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000685 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000686 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000687 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000688 return True
689
690 def EchoTitleHandler(self):
691 """This handler is like Echo, but sets the page title to the request."""
692
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000693 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000694 return False
695
696 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000697 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000698 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000699 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000700 self.wfile.write('<html><head><title>')
701 self.wfile.write(request)
702 self.wfile.write('</title></head></html>')
703 return True
704
705 def EchoAllHandler(self):
706 """This handler yields a (more) human-readable page listing information
707 about the request header & contents."""
708
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000709 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000710 return False
711
712 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000713 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000714 self.end_headers()
715 self.wfile.write('<html><head><style>'
716 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
717 '</style></head><body>'
718 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000719 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000720 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000721
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000722 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000723 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000724 params = cgi.parse_qs(qs, keep_blank_values=1)
725
726 for param in params:
727 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000728
729 self.wfile.write('</pre>')
730
731 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
732
733 self.wfile.write('</body></html>')
734 return True
735
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000736 def EchoMultipartPostHandler(self):
737 """This handler echoes received multipart post data as json format."""
738
739 if not (self._ShouldHandleRequest("/echomultipartpost") or
740 self._ShouldHandleRequest("/searchbyimage")):
741 return False
742
743 content_type, parameters = cgi.parse_header(
744 self.headers.getheader('content-type'))
745 if content_type == 'multipart/form-data':
746 post_multipart = cgi.parse_multipart(self.rfile, parameters)
747 elif content_type == 'application/x-www-form-urlencoded':
748 raise Exception('POST by application/x-www-form-urlencoded is '
749 'not implemented.')
750 else:
751 post_multipart = {}
752
753 # Since the data can be binary, we encode them by base64.
754 post_multipart_base64_encoded = {}
755 for field, values in post_multipart.items():
756 post_multipart_base64_encoded[field] = [base64.b64encode(value)
757 for value in values]
758
759 result = {'POST_multipart' : post_multipart_base64_encoded}
760
761 self.send_response(200)
762 self.send_header("Content-type", "text/plain")
763 self.end_headers()
764 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
765 return True
766
initial.commit94958cf2008-07-26 22:42:52 +0000767 def DownloadHandler(self):
768 """This handler sends a downloadable file with or without reporting
769 the size (6K)."""
770
771 if self.path.startswith("/download-unknown-size"):
772 send_length = False
773 elif self.path.startswith("/download-known-size"):
774 send_length = True
775 else:
776 return False
777
778 #
779 # The test which uses this functionality is attempting to send
780 # small chunks of data to the client. Use a fairly large buffer
781 # so that we'll fill chrome's IO buffer enough to force it to
782 # actually write the data.
783 # See also the comments in the client-side of this test in
784 # download_uitest.cc
785 #
786 size_chunk1 = 35*1024
787 size_chunk2 = 10*1024
788
789 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000790 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000791 self.send_header('Cache-Control', 'max-age=0')
792 if send_length:
793 self.send_header('Content-Length', size_chunk1 + size_chunk2)
794 self.end_headers()
795
796 # First chunk of data:
797 self.wfile.write("*" * size_chunk1)
798 self.wfile.flush()
799
800 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000801 self.server.wait_for_download = True
802 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000803 self.server.handle_request()
804
805 # Second chunk of data:
806 self.wfile.write("*" * size_chunk2)
807 return True
808
809 def DownloadFinishHandler(self):
810 """This handler just tells the server to finish the current download."""
811
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000812 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000813 return False
814
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000815 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000816 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000817 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000818 self.send_header('Cache-Control', 'max-age=0')
819 self.end_headers()
820 return True
821
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000822 def _ReplaceFileData(self, data, query_parameters):
823 """Replaces matching substrings in a file.
824
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000825 If the 'replace_text' URL query parameter is present, it is expected to be
826 of the form old_text:new_text, which indicates that any old_text strings in
827 the file are replaced with new_text. Multiple 'replace_text' parameters may
828 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000829
830 If the parameters are not present, |data| is returned.
831 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000832
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000833 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000834 replace_text_values = query_dict.get('replace_text', [])
835 for replace_text_value in replace_text_values:
836 replace_text_args = replace_text_value.split(':')
837 if len(replace_text_args) != 2:
838 raise ValueError(
839 'replace_text must be of form old_text:new_text. Actual value: %s' %
840 replace_text_value)
841 old_text_b64, new_text_b64 = replace_text_args
842 old_text = base64.urlsafe_b64decode(old_text_b64)
843 new_text = base64.urlsafe_b64decode(new_text_b64)
844 data = data.replace(old_text, new_text)
845 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000846
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000847 def ZipFileHandler(self):
848 """This handler sends the contents of the requested file in compressed form.
849 Can pass in a parameter that specifies that the content length be
850 C - the compressed size (OK),
851 U - the uncompressed size (Non-standard, but handled),
852 S - less than compressed (OK because we keep going),
853 M - larger than compressed but less than uncompressed (an error),
854 L - larger than uncompressed (an error)
855 Example: compressedfiles/Picture_1.doc?C
856 """
857
858 prefix = "/compressedfiles/"
859 if not self.path.startswith(prefix):
860 return False
861
862 # Consume a request body if present.
863 if self.command == 'POST' or self.command == 'PUT' :
864 self.ReadRequestBody()
865
866 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
867
868 if not query in ('C', 'U', 'S', 'M', 'L'):
869 return False
870
871 sub_path = url_path[len(prefix):]
872 entries = sub_path.split('/')
873 file_path = os.path.join(self.server.data_dir, *entries)
874 if os.path.isdir(file_path):
875 file_path = os.path.join(file_path, 'index.html')
876
877 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000878 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000879 self.send_error(404)
880 return True
881
882 f = open(file_path, "rb")
883 data = f.read()
884 uncompressed_len = len(data)
885 f.close()
886
887 # Compress the data.
888 data = zlib.compress(data)
889 compressed_len = len(data)
890
891 content_length = compressed_len
892 if query == 'U':
893 content_length = uncompressed_len
894 elif query == 'S':
895 content_length = compressed_len / 2
896 elif query == 'M':
897 content_length = (compressed_len + uncompressed_len) / 2
898 elif query == 'L':
899 content_length = compressed_len + uncompressed_len
900
901 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000902 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000903 self.send_header('Content-encoding', 'deflate')
904 self.send_header('Connection', 'close')
905 self.send_header('Content-Length', content_length)
906 self.send_header('ETag', '\'' + file_path + '\'')
907 self.end_headers()
908
909 self.wfile.write(data)
910
911 return True
912
initial.commit94958cf2008-07-26 22:42:52 +0000913 def FileHandler(self):
914 """This handler sends the contents of the requested file. Wow, it's like
915 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000916
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000917 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000918 if not self.path.startswith(prefix):
919 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000920 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000921
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000922 def PostOnlyFileHandler(self):
923 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000924
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000925 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000926 if not self.path.startswith(prefix):
927 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000928 return self._FileHandlerHelper(prefix)
929
930 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000931 request_body = ''
932 if self.command == 'POST' or self.command == 'PUT':
933 # Consume a request body if present.
934 request_body = self.ReadRequestBody()
935
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000936 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000937 query_dict = cgi.parse_qs(query)
938
939 expected_body = query_dict.get('expected_body', [])
940 if expected_body and request_body not in expected_body:
941 self.send_response(404)
942 self.end_headers()
943 self.wfile.write('')
944 return True
945
946 expected_headers = query_dict.get('expected_headers', [])
947 for expected_header in expected_headers:
948 header_name, expected_value = expected_header.split(':')
949 if self.headers.getheader(header_name) != expected_value:
950 self.send_response(404)
951 self.end_headers()
952 self.wfile.write('')
953 return True
954
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000955 sub_path = url_path[len(prefix):]
956 entries = sub_path.split('/')
957 file_path = os.path.join(self.server.data_dir, *entries)
958 if os.path.isdir(file_path):
959 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000960
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000961 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000962 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000963 self.send_error(404)
964 return True
965
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000966 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000967 data = f.read()
968 f.close()
969
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000970 data = self._ReplaceFileData(data, query)
971
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000972 old_protocol_version = self.protocol_version
973
initial.commit94958cf2008-07-26 22:42:52 +0000974 # If file.mock-http-headers exists, it contains the headers we
975 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000976 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000977 if os.path.isfile(headers_path):
978 f = open(headers_path, "r")
979
980 # "HTTP/1.1 200 OK"
981 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000982 http_major, http_minor, status_code = re.findall(
983 'HTTP/(\d+).(\d+) (\d+)', response)[0]
984 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000985 self.send_response(int(status_code))
986
987 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000988 header_values = re.findall('(\S+):\s*(.*)', line)
989 if len(header_values) > 0:
990 # "name: value"
991 name, value = header_values[0]
992 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000993 f.close()
994 else:
995 # Could be more generic once we support mime-type sniffing, but for
996 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000997
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000998 range_header = self.headers.get('Range')
999 if range_header and range_header.startswith('bytes='):
1000 # Note this doesn't handle all valid byte range_header values (i.e.
1001 # left open ended ones), just enough for what we needed so far.
1002 range_header = range_header[6:].split('-')
1003 start = int(range_header[0])
1004 if range_header[1]:
1005 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001006 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001007 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001008
1009 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001010 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1011 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001012 self.send_header('Content-Range', content_range)
1013 data = data[start: end + 1]
1014 else:
1015 self.send_response(200)
1016
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001017 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001018 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001019 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001020 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001021 self.end_headers()
1022
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001023 if (self.command != 'HEAD'):
1024 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001025
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001026 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001027 return True
1028
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001029 def SetCookieHandler(self):
1030 """This handler just sets a cookie, for testing cookie handling."""
1031
1032 if not self._ShouldHandleRequest("/set-cookie"):
1033 return False
1034
1035 query_char = self.path.find('?')
1036 if query_char != -1:
1037 cookie_values = self.path[query_char + 1:].split('&')
1038 else:
1039 cookie_values = ("",)
1040 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001041 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001042 for cookie_value in cookie_values:
1043 self.send_header('Set-Cookie', '%s' % cookie_value)
1044 self.end_headers()
1045 for cookie_value in cookie_values:
1046 self.wfile.write('%s' % cookie_value)
1047 return True
1048
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001049 def SetManyCookiesHandler(self):
1050 """This handler just sets a given number of cookies, for testing handling
1051 of large numbers of cookies."""
1052
1053 if not self._ShouldHandleRequest("/set-many-cookies"):
1054 return False
1055
1056 query_char = self.path.find('?')
1057 if query_char != -1:
1058 num_cookies = int(self.path[query_char + 1:])
1059 else:
1060 num_cookies = 0
1061 self.send_response(200)
1062 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001063 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001064 self.send_header('Set-Cookie', 'a=')
1065 self.end_headers()
1066 self.wfile.write('%d cookies were sent' % num_cookies)
1067 return True
1068
mattm@chromium.org983fc462012-06-30 00:52:08 +00001069 def ExpectAndSetCookieHandler(self):
1070 """Expects some cookies to be sent, and if they are, sets more cookies.
1071
1072 The expect parameter specifies a required cookie. May be specified multiple
1073 times.
1074 The set parameter specifies a cookie to set if all required cookies are
1075 preset. May be specified multiple times.
1076 The data parameter specifies the response body data to be returned."""
1077
1078 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1079 return False
1080
1081 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1082 query_dict = cgi.parse_qs(query)
1083 cookies = set()
1084 if 'Cookie' in self.headers:
1085 cookie_header = self.headers.getheader('Cookie')
1086 cookies.update([s.strip() for s in cookie_header.split(';')])
1087 got_all_expected_cookies = True
1088 for expected_cookie in query_dict.get('expect', []):
1089 if expected_cookie not in cookies:
1090 got_all_expected_cookies = False
1091 self.send_response(200)
1092 self.send_header('Content-Type', 'text/html')
1093 if got_all_expected_cookies:
1094 for cookie_value in query_dict.get('set', []):
1095 self.send_header('Set-Cookie', '%s' % cookie_value)
1096 self.end_headers()
1097 for data_value in query_dict.get('data', []):
1098 self.wfile.write(data_value)
1099 return True
1100
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001101 def SetHeaderHandler(self):
1102 """This handler sets a response header. Parameters are in the
1103 key%3A%20value&key2%3A%20value2 format."""
1104
1105 if not self._ShouldHandleRequest("/set-header"):
1106 return False
1107
1108 query_char = self.path.find('?')
1109 if query_char != -1:
1110 headers_values = self.path[query_char + 1:].split('&')
1111 else:
1112 headers_values = ("",)
1113 self.send_response(200)
1114 self.send_header('Content-Type', 'text/html')
1115 for header_value in headers_values:
1116 header_value = urllib.unquote(header_value)
1117 (key, value) = header_value.split(': ', 1)
1118 self.send_header(key, value)
1119 self.end_headers()
1120 for header_value in headers_values:
1121 self.wfile.write('%s' % header_value)
1122 return True
1123
initial.commit94958cf2008-07-26 22:42:52 +00001124 def AuthBasicHandler(self):
1125 """This handler tests 'Basic' authentication. It just sends a page with
1126 title 'user/pass' if you succeed."""
1127
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001128 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001129 return False
1130
1131 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001132 expected_password = 'secret'
1133 realm = 'testrealm'
1134 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001135
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001136 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1137 query_params = cgi.parse_qs(query, True)
1138 if 'set-cookie-if-challenged' in query_params:
1139 set_cookie_if_challenged = True
1140 if 'password' in query_params:
1141 expected_password = query_params['password'][0]
1142 if 'realm' in query_params:
1143 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001144
initial.commit94958cf2008-07-26 22:42:52 +00001145 auth = self.headers.getheader('authorization')
1146 try:
1147 if not auth:
1148 raise Exception('no auth')
1149 b64str = re.findall(r'Basic (\S+)', auth)[0]
1150 userpass = base64.b64decode(b64str)
1151 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001152 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001153 raise Exception('wrong password')
1154 except Exception, e:
1155 # Authentication failed.
1156 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001157 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001158 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001159 if set_cookie_if_challenged:
1160 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001161 self.end_headers()
1162 self.wfile.write('<html><head>')
1163 self.wfile.write('<title>Denied: %s</title>' % e)
1164 self.wfile.write('</head><body>')
1165 self.wfile.write('auth=%s<p>' % auth)
1166 self.wfile.write('b64str=%s<p>' % b64str)
1167 self.wfile.write('username: %s<p>' % username)
1168 self.wfile.write('userpass: %s<p>' % userpass)
1169 self.wfile.write('password: %s<p>' % password)
1170 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1171 self.wfile.write('</body></html>')
1172 return True
1173
1174 # Authentication successful. (Return a cachable response to allow for
1175 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001176 old_protocol_version = self.protocol_version
1177 self.protocol_version = "HTTP/1.1"
1178
initial.commit94958cf2008-07-26 22:42:52 +00001179 if_none_match = self.headers.getheader('if-none-match')
1180 if if_none_match == "abc":
1181 self.send_response(304)
1182 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001183 elif url_path.endswith(".gif"):
1184 # Using chrome/test/data/google/logo.gif as the test image
1185 test_image_path = ['google', 'logo.gif']
1186 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1187 if not os.path.isfile(gif_path):
1188 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001189 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001190 return True
1191
1192 f = open(gif_path, "rb")
1193 data = f.read()
1194 f.close()
1195
1196 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001197 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001198 self.send_header('Cache-control', 'max-age=60000')
1199 self.send_header('Etag', 'abc')
1200 self.end_headers()
1201 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001202 else:
1203 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001204 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001205 self.send_header('Cache-control', 'max-age=60000')
1206 self.send_header('Etag', 'abc')
1207 self.end_headers()
1208 self.wfile.write('<html><head>')
1209 self.wfile.write('<title>%s/%s</title>' % (username, password))
1210 self.wfile.write('</head><body>')
1211 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001212 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001213 self.wfile.write('</body></html>')
1214
rvargas@google.com54453b72011-05-19 01:11:11 +00001215 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001216 return True
1217
tonyg@chromium.org75054202010-03-31 22:06:10 +00001218 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001219 """Returns a nonce that's stable per request path for the server's lifetime.
1220 This is a fake implementation. A real implementation would only use a given
1221 nonce a single time (hence the name n-once). However, for the purposes of
1222 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001223
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001224 Args:
1225 force_reset: Iff set, the nonce will be changed. Useful for testing the
1226 "stale" response.
1227 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001228
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001229 if force_reset or not self.server.nonce_time:
1230 self.server.nonce_time = time.time()
1231 return hashlib.md5('privatekey%s%d' %
1232 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001233
1234 def AuthDigestHandler(self):
1235 """This handler tests 'Digest' authentication.
1236
1237 It just sends a page with title 'user/pass' if you succeed.
1238
1239 A stale response is sent iff "stale" is present in the request path.
1240 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001241
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001242 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001243 return False
1244
tonyg@chromium.org75054202010-03-31 22:06:10 +00001245 stale = 'stale' in self.path
1246 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001247 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001248 password = 'secret'
1249 realm = 'testrealm'
1250
1251 auth = self.headers.getheader('authorization')
1252 pairs = {}
1253 try:
1254 if not auth:
1255 raise Exception('no auth')
1256 if not auth.startswith('Digest'):
1257 raise Exception('not digest')
1258 # Pull out all the name="value" pairs as a dictionary.
1259 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1260
1261 # Make sure it's all valid.
1262 if pairs['nonce'] != nonce:
1263 raise Exception('wrong nonce')
1264 if pairs['opaque'] != opaque:
1265 raise Exception('wrong opaque')
1266
1267 # Check the 'response' value and make sure it matches our magic hash.
1268 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001269 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001270 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001271 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001272 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001273 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001274 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1275 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001276 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001277
1278 if pairs['response'] != response:
1279 raise Exception('wrong password')
1280 except Exception, e:
1281 # Authentication failed.
1282 self.send_response(401)
1283 hdr = ('Digest '
1284 'realm="%s", '
1285 'domain="/", '
1286 'qop="auth", '
1287 'algorithm=MD5, '
1288 'nonce="%s", '
1289 'opaque="%s"') % (realm, nonce, opaque)
1290 if stale:
1291 hdr += ', stale="TRUE"'
1292 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001293 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001294 self.end_headers()
1295 self.wfile.write('<html><head>')
1296 self.wfile.write('<title>Denied: %s</title>' % e)
1297 self.wfile.write('</head><body>')
1298 self.wfile.write('auth=%s<p>' % auth)
1299 self.wfile.write('pairs=%s<p>' % pairs)
1300 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1301 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1302 self.wfile.write('</body></html>')
1303 return True
1304
1305 # Authentication successful.
1306 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001307 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001308 self.end_headers()
1309 self.wfile.write('<html><head>')
1310 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1311 self.wfile.write('</head><body>')
1312 self.wfile.write('auth=%s<p>' % auth)
1313 self.wfile.write('pairs=%s<p>' % pairs)
1314 self.wfile.write('</body></html>')
1315
1316 return True
1317
1318 def SlowServerHandler(self):
1319 """Wait for the user suggested time before responding. The syntax is
1320 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001321
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001322 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001323 return False
1324 query_char = self.path.find('?')
1325 wait_sec = 1.0
1326 if query_char >= 0:
1327 try:
davidben05f82202015-03-31 13:48:07 -07001328 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001329 except ValueError:
1330 pass
1331 time.sleep(wait_sec)
1332 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001333 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001334 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001335 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001336 return True
1337
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001338 def ChunkedServerHandler(self):
1339 """Send chunked response. Allows to specify chunks parameters:
1340 - waitBeforeHeaders - ms to wait before sending headers
1341 - waitBetweenChunks - ms to wait between chunks
1342 - chunkSize - size of each chunk in bytes
1343 - chunksNumber - number of chunks
1344 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1345 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001346
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001347 if not self._ShouldHandleRequest("/chunked"):
1348 return False
1349 query_char = self.path.find('?')
1350 chunkedSettings = {'waitBeforeHeaders' : 0,
1351 'waitBetweenChunks' : 0,
1352 'chunkSize' : 5,
1353 'chunksNumber' : 5}
1354 if query_char >= 0:
1355 params = self.path[query_char + 1:].split('&')
1356 for param in params:
1357 keyValue = param.split('=')
1358 if len(keyValue) == 2:
1359 try:
1360 chunkedSettings[keyValue[0]] = int(keyValue[1])
1361 except ValueError:
1362 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001363 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001364 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1365 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001366 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001367 self.send_header('Connection', 'close')
1368 self.send_header('Transfer-Encoding', 'chunked')
1369 self.end_headers()
1370 # Chunked encoding: sending all chunks, then final zero-length chunk and
1371 # then final CRLF.
1372 for i in range(0, chunkedSettings['chunksNumber']):
1373 if i > 0:
1374 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1375 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001376 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001377 self.sendChunkHelp('')
1378 return True
1379
creis@google.com2f4f6a42011-03-25 19:44:19 +00001380 def NoContentHandler(self):
1381 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001382
creis@google.com2f4f6a42011-03-25 19:44:19 +00001383 if not self._ShouldHandleRequest("/nocontent"):
1384 return False
1385 self.send_response(204)
1386 self.end_headers()
1387 return True
1388
initial.commit94958cf2008-07-26 22:42:52 +00001389 def ServerRedirectHandler(self):
1390 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001391 '/server-redirect?http://foo.bar/asdf' to redirect to
1392 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001393
1394 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001395 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001396 return False
1397
1398 query_char = self.path.find('?')
1399 if query_char < 0 or len(self.path) <= query_char + 1:
1400 self.sendRedirectHelp(test_name)
1401 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001402 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001403
1404 self.send_response(301) # moved permanently
1405 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001406 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001407 self.end_headers()
1408 self.wfile.write('<html><head>')
1409 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1410
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001411 return True
initial.commit94958cf2008-07-26 22:42:52 +00001412
naskoe7a0d0d2014-09-29 08:53:05 -07001413 def CrossSiteRedirectHandler(self):
1414 """Sends a server redirect to the given site. The syntax is
1415 '/cross-site/hostname/...' to redirect to //hostname/...
1416 It is used to navigate between different Sites, causing
1417 cross-site/cross-process navigations in the browser."""
1418
1419 test_name = "/cross-site"
1420 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001421 return False
1422
1423 params = urllib.unquote(self.path[(len(test_name) + 1):])
1424 slash = params.find('/')
1425 if slash < 0:
1426 self.sendRedirectHelp(test_name)
1427 return True
1428
1429 host = params[:slash]
1430 path = params[(slash+1):]
1431 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1432
1433 self.send_response(301) # moved permanently
1434 self.send_header('Location', dest)
1435 self.send_header('Content-Type', 'text/html')
1436 self.end_headers()
1437 self.wfile.write('<html><head>')
1438 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1439
1440 return True
1441
initial.commit94958cf2008-07-26 22:42:52 +00001442 def ClientRedirectHandler(self):
1443 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001444 '/client-redirect?http://foo.bar/asdf' to redirect to
1445 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001446
1447 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001448 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001449 return False
1450
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001451 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001452 if query_char < 0 or len(self.path) <= query_char + 1:
1453 self.sendRedirectHelp(test_name)
1454 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001455 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001456
1457 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001458 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001459 self.end_headers()
1460 self.wfile.write('<html><head>')
1461 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1462 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1463
1464 return True
1465
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001466 def GetSSLSessionCacheHandler(self):
1467 """Send a reply containing a log of the session cache operations."""
1468
1469 if not self._ShouldHandleRequest('/ssl-session-cache'):
1470 return False
1471
1472 self.send_response(200)
1473 self.send_header('Content-Type', 'text/plain')
1474 self.end_headers()
1475 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001476 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001477 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001478 self.wfile.write('Pass --https-record-resume in order to use' +
1479 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001480 return True
1481
1482 for (action, sessionID) in log:
1483 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001484 return True
1485
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001486 def SSLManySmallRecords(self):
1487 """Sends a reply consisting of a variety of small writes. These will be
1488 translated into a series of small SSL records when used over an HTTPS
1489 server."""
1490
1491 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1492 return False
1493
1494 self.send_response(200)
1495 self.send_header('Content-Type', 'text/plain')
1496 self.end_headers()
1497
1498 # Write ~26K of data, in 1350 byte chunks
1499 for i in xrange(20):
1500 self.wfile.write('*' * 1350)
1501 self.wfile.flush()
1502 return True
1503
agl@chromium.org04700be2013-03-02 18:40:41 +00001504 def GetChannelID(self):
1505 """Send a reply containing the hashed ChannelID that the client provided."""
1506
1507 if not self._ShouldHandleRequest('/channel-id'):
1508 return False
1509
1510 self.send_response(200)
1511 self.send_header('Content-Type', 'text/plain')
1512 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001513 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001514 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1515 return True
1516
nharper08eae822016-01-25 15:54:14 -08001517 def GetTokenBindingEKM(self):
1518 """Send a reply containing the EKM value for token binding from the TLS
1519 layer."""
1520
1521 if not self._ShouldHandleRequest('/tokbind-ekm'):
1522 return False
1523
1524 ekm = self.server.tlsConnection.exportKeyingMaterial(
1525 "EXPORTER-Token-Binding", "", False, 32)
1526 self.send_response(200)
1527 self.send_header('Content-Type', 'application/octet-stream')
1528 self.end_headers()
1529 self.wfile.write(ekm)
1530 return True
1531
nharpercb1adc32016-03-30 16:05:48 -07001532 def ForwardTokenBindingHeader(self):
nharpere758cd12016-07-13 17:49:36 -07001533 """Send a redirect that sets the Include-Referred-Token-Binding-ID
nharpercb1adc32016-03-30 16:05:48 -07001534 header."""
1535
1536 test_name = '/forward-tokbind'
1537 if not self._ShouldHandleRequest(test_name):
1538 return False
1539
1540 query_char = self.path.find('?')
1541 if query_char < 0 or len(self.path) <= query_char + 1:
1542 self.sendRedirectHelp(test_name)
1543 return True
1544 dest = urllib.unquote(self.path[query_char + 1:])
1545
1546 self.send_response(302)
1547 self.send_header('Location', dest)
nharpere758cd12016-07-13 17:49:36 -07001548 self.send_header('Include-Referred-Token-Binding-ID', 'true')
nharpercb1adc32016-03-30 16:05:48 -07001549 self.end_headers()
1550 return True
1551
pneubeckfd4f0442015-08-07 04:55:10 -07001552 def GetClientCert(self):
1553 """Send a reply whether a client certificate was provided."""
1554
1555 if not self._ShouldHandleRequest('/client-cert'):
1556 return False
1557
1558 self.send_response(200)
1559 self.send_header('Content-Type', 'text/plain')
1560 self.end_headers()
1561
1562 cert_chain = self.server.tlsConnection.session.clientCertChain
1563 if cert_chain != None:
1564 self.wfile.write('got client cert with fingerprint: ' +
1565 cert_chain.getFingerprint())
1566 else:
1567 self.wfile.write('got no client cert')
1568 return True
1569
davidben599e7e72014-09-03 16:19:09 -07001570 def ClientCipherListHandler(self):
1571 """Send a reply containing the cipher suite list that the client
1572 provided. Each cipher suite value is serialized in decimal, followed by a
1573 newline."""
1574
1575 if not self._ShouldHandleRequest('/client-cipher-list'):
1576 return False
1577
1578 self.send_response(200)
1579 self.send_header('Content-Type', 'text/plain')
1580 self.end_headers()
1581
davidben11682512014-10-06 21:09:11 -07001582 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1583 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001584 return True
1585
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001586 def CloseSocketHandler(self):
1587 """Closes the socket without sending anything."""
1588
1589 if not self._ShouldHandleRequest('/close-socket'):
1590 return False
1591
1592 self.wfile.close()
1593 return True
1594
initial.commit94958cf2008-07-26 22:42:52 +00001595 def DefaultResponseHandler(self):
1596 """This is the catch-all response handler for requests that aren't handled
1597 by one of the special handlers above.
1598 Note that we specify the content-length as without it the https connection
1599 is not closed properly (and the browser keeps expecting data)."""
1600
1601 contents = "Default response given for path: " + self.path
1602 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001603 self.send_header('Content-Type', 'text/html')
1604 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001605 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001606 if (self.command != 'HEAD'):
1607 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001608 return True
1609
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001610 def RedirectConnectHandler(self):
1611 """Sends a redirect to the CONNECT request for www.redirect.com. This
1612 response is not specified by the RFC, so the browser should not follow
1613 the redirect."""
1614
1615 if (self.path.find("www.redirect.com") < 0):
1616 return False
1617
1618 dest = "http://www.destination.com/foo.js"
1619
1620 self.send_response(302) # moved temporarily
1621 self.send_header('Location', dest)
1622 self.send_header('Connection', 'close')
1623 self.end_headers()
1624 return True
1625
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001626 def ServerAuthConnectHandler(self):
1627 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1628 response doesn't make sense because the proxy server cannot request
1629 server authentication."""
1630
1631 if (self.path.find("www.server-auth.com") < 0):
1632 return False
1633
1634 challenge = 'Basic realm="WallyWorld"'
1635
1636 self.send_response(401) # unauthorized
1637 self.send_header('WWW-Authenticate', challenge)
1638 self.send_header('Connection', 'close')
1639 self.end_headers()
1640 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001641
1642 def DefaultConnectResponseHandler(self):
1643 """This is the catch-all response handler for CONNECT requests that aren't
1644 handled by one of the special handlers above. Real Web servers respond
1645 with 400 to CONNECT requests."""
1646
1647 contents = "Your client has issued a malformed or illegal request."
1648 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001649 self.send_header('Content-Type', 'text/html')
1650 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001651 self.end_headers()
1652 self.wfile.write(contents)
1653 return True
1654
initial.commit94958cf2008-07-26 22:42:52 +00001655 # called by the redirect handling function when there is no parameter
1656 def sendRedirectHelp(self, redirect_name):
1657 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001658 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001659 self.end_headers()
1660 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1661 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1662 self.wfile.write('</body></html>')
1663
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001664 # called by chunked handling function
1665 def sendChunkHelp(self, chunk):
1666 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1667 self.wfile.write('%X\r\n' % len(chunk))
1668 self.wfile.write(chunk)
1669 self.wfile.write('\r\n')
1670
akalin@chromium.org154bb132010-11-12 02:20:27 +00001671
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001672class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001673 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001674 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001675 self.ocsp_response = socket_server.ocsp_response
mattm10ede842016-11-29 11:57:16 -08001676 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001677 testserver_base.BasePageHandler.__init__(self, request, client_address,
1678 socket_server, [], handlers, [],
1679 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001680
1681 def OCSPResponse(self):
mattm10ede842016-11-29 11:57:16 -08001682 if not self._ShouldHandleRequest("/ocsp"):
1683 return False
1684 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001685 self.send_response(200)
1686 self.send_header('Content-Type', 'application/ocsp-response')
1687 self.send_header('Content-Length', str(len(self.ocsp_response)))
1688 self.end_headers()
1689
1690 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001691
mattm10ede842016-11-29 11:57:16 -08001692 def CaIssuersResponse(self):
1693 if not self._ShouldHandleRequest("/ca_issuers"):
1694 return False
1695 print 'handling ca_issuers request'
1696 self.send_response(200)
1697 self.send_header('Content-Type', 'application/pkix-cert')
1698 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1699 self.end_headers()
1700
1701 self.wfile.write(self.ca_issuers_response)
1702
mattm@chromium.org830a3712012-11-07 23:00:07 +00001703
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001704class TCPEchoHandler(SocketServer.BaseRequestHandler):
1705 """The RequestHandler class for TCP echo server.
1706
1707 It is instantiated once per connection to the server, and overrides the
1708 handle() method to implement communication to the client.
1709 """
1710
1711 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001712 """Handles the request from the client and constructs a response."""
1713
1714 data = self.request.recv(65536).strip()
1715 # Verify the "echo request" message received from the client. Send back
1716 # "echo response" message if "echo request" message is valid.
1717 try:
1718 return_data = echo_message.GetEchoResponseData(data)
1719 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001720 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001721 except ValueError:
1722 return
1723
1724 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001725
1726
1727class UDPEchoHandler(SocketServer.BaseRequestHandler):
1728 """The RequestHandler class for UDP echo server.
1729
1730 It is instantiated once per connection to the server, and overrides the
1731 handle() method to implement communication to the client.
1732 """
1733
1734 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001735 """Handles the request from the client and constructs a response."""
1736
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001737 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001738 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001739 # Verify the "echo request" message received from the client. Send back
1740 # "echo response" message if "echo request" message is valid.
1741 try:
1742 return_data = echo_message.GetEchoResponseData(data)
1743 if not return_data:
1744 return
1745 except ValueError:
1746 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001747 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001748
1749
bashi@chromium.org33233532012-09-08 17:37:24 +00001750class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1751 """A request handler that behaves as a proxy server which requires
1752 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1753 """
1754
1755 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001756 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001757
1758 def parse_request(self):
1759 """Overrides parse_request to check credential."""
1760
1761 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1762 return False
1763
1764 auth = self.headers.getheader('Proxy-Authorization')
1765 if auth != self._AUTH_CREDENTIAL:
1766 self.send_response(407)
1767 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1768 self.end_headers()
1769 return False
1770
1771 return True
1772
1773 def _start_read_write(self, sock):
1774 sock.setblocking(0)
1775 self.request.setblocking(0)
1776 rlist = [self.request, sock]
1777 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001778 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001779 if errors:
1780 self.send_response(500)
1781 self.end_headers()
1782 return
1783 for s in ready_sockets:
1784 received = s.recv(1024)
1785 if len(received) == 0:
1786 return
1787 if s == self.request:
1788 other = sock
1789 else:
1790 other = self.request
1791 other.send(received)
1792
1793 def _do_common_method(self):
1794 url = urlparse.urlparse(self.path)
1795 port = url.port
1796 if not port:
1797 if url.scheme == 'http':
1798 port = 80
1799 elif url.scheme == 'https':
1800 port = 443
1801 if not url.hostname or not port:
1802 self.send_response(400)
1803 self.end_headers()
1804 return
1805
1806 if len(url.path) == 0:
1807 path = '/'
1808 else:
1809 path = url.path
1810 if len(url.query) > 0:
1811 path = '%s?%s' % (url.path, url.query)
1812
1813 sock = None
1814 try:
1815 sock = socket.create_connection((url.hostname, port))
1816 sock.send('%s %s %s\r\n' % (
1817 self.command, path, self.protocol_version))
1818 for header in self.headers.headers:
1819 header = header.strip()
1820 if (header.lower().startswith('connection') or
1821 header.lower().startswith('proxy')):
1822 continue
1823 sock.send('%s\r\n' % header)
1824 sock.send('\r\n')
1825 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001826 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001827 self.send_response(500)
1828 self.end_headers()
1829 finally:
1830 if sock is not None:
1831 sock.close()
1832
1833 def do_CONNECT(self):
1834 try:
1835 pos = self.path.rfind(':')
1836 host = self.path[:pos]
1837 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001838 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001839 self.send_response(400)
1840 self.end_headers()
1841
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001842 if BasicAuthProxyRequestHandler.redirect_connect_to_localhost:
1843 host = "127.0.0.1"
1844
bashi@chromium.org33233532012-09-08 17:37:24 +00001845 try:
1846 sock = socket.create_connection((host, port))
1847 self.send_response(200, 'Connection established')
1848 self.end_headers()
1849 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001850 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001851 self.send_response(500)
1852 self.end_headers()
1853 finally:
1854 sock.close()
1855
1856 def do_GET(self):
1857 self._do_common_method()
1858
1859 def do_HEAD(self):
1860 self._do_common_method()
1861
1862
mattm@chromium.org830a3712012-11-07 23:00:07 +00001863class ServerRunner(testserver_base.TestServerRunner):
1864 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001865
mattm@chromium.org830a3712012-11-07 23:00:07 +00001866 def __init__(self):
1867 super(ServerRunner, self).__init__()
1868 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001869
mattm@chromium.org830a3712012-11-07 23:00:07 +00001870 def __make_data_dir(self):
1871 if self.options.data_dir:
1872 if not os.path.isdir(self.options.data_dir):
1873 raise testserver_base.OptionError('specified data dir not found: ' +
1874 self.options.data_dir + ' exiting...')
1875 my_data_dir = self.options.data_dir
1876 else:
1877 # Create the default path to our data dir, relative to the exe dir.
1878 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1879 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001880
mattm@chromium.org830a3712012-11-07 23:00:07 +00001881 #TODO(ibrar): Must use Find* funtion defined in google\tools
1882 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001883
mattm@chromium.org830a3712012-11-07 23:00:07 +00001884 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001885
mattm@chromium.org830a3712012-11-07 23:00:07 +00001886 def create_server(self, server_data):
1887 port = self.options.port
1888 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001889
estark21667d62015-04-08 21:00:16 -07001890 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1891 # will result in a call to |getaddrinfo|, which fails with "nodename
1892 # nor servname provided" for localhost:0 on 10.6.
1893 if self.options.server_type == SERVER_WEBSOCKET and \
1894 host == "localhost" and \
1895 port == 0:
1896 host = "127.0.0.1"
1897
mattm@chromium.org830a3712012-11-07 23:00:07 +00001898 if self.options.server_type == SERVER_HTTP:
1899 if self.options.https:
1900 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001901 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001902 if self.options.cert_and_key_file:
1903 if not os.path.isfile(self.options.cert_and_key_file):
1904 raise testserver_base.OptionError(
1905 'specified server cert file not found: ' +
1906 self.options.cert_and_key_file + ' exiting...')
1907 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001908 elif self.options.aia_intermediate:
1909 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1910 print ('AIA server started on %s:%d...' %
1911 (host, self.__ocsp_server.server_port))
1912
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001913 ocsp_server_port = self.__ocsp_server.server_port
1914 if self.options.ocsp_proxy_port_number != 0:
1915 ocsp_server_port = self.options.ocsp_proxy_port_number
1916 server_data['ocsp_port'] = self.__ocsp_server.server_port
1917
mattm10ede842016-11-29 11:57:16 -08001918 (pem_cert_and_key, intermediate_cert_der) = \
1919 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001920 subject = self.options.cert_common_name,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001921 ca_issuers_url =
1922 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08001923 serial = self.options.cert_serial)
1924
1925 self.__ocsp_server.ocsp_response = None
1926 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001927 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001928 # generate a new certificate and run an OCSP server for it.
1929 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001930 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001931 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001932
dadrian4ccf51c2016-07-20 15:36:58 -07001933 ocsp_states = list()
1934 for ocsp_state_arg in self.options.ocsp.split(':'):
1935 if ocsp_state_arg == 'ok':
1936 ocsp_state = minica.OCSP_STATE_GOOD
1937 elif ocsp_state_arg == 'revoked':
1938 ocsp_state = minica.OCSP_STATE_REVOKED
1939 elif ocsp_state_arg == 'invalid':
1940 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1941 elif ocsp_state_arg == 'unauthorized':
1942 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1943 elif ocsp_state_arg == 'unknown':
1944 ocsp_state = minica.OCSP_STATE_UNKNOWN
1945 elif ocsp_state_arg == 'later':
1946 ocsp_state = minica.OCSP_STATE_TRY_LATER
1947 elif ocsp_state_arg == 'invalid_data':
1948 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1949 elif ocsp_state_arg == "mismatched_serial":
1950 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1951 else:
1952 raise testserver_base.OptionError('unknown OCSP status: ' +
1953 ocsp_state_arg)
1954 ocsp_states.append(ocsp_state)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001955
dadrian4ccf51c2016-07-20 15:36:58 -07001956 if len(ocsp_states) > 1:
1957 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1958 raise testserver_base.OptionError('Multiple OCSP responses '
1959 'incompatible with states ' + str(ocsp_states))
1960
1961 ocsp_dates = list()
1962 for ocsp_date_arg in self.options.ocsp_date.split(':'):
1963 if ocsp_date_arg == 'valid':
1964 ocsp_date = minica.OCSP_DATE_VALID
1965 elif ocsp_date_arg == 'old':
1966 ocsp_date = minica.OCSP_DATE_OLD
1967 elif ocsp_date_arg == 'early':
1968 ocsp_date = minica.OCSP_DATE_EARLY
1969 elif ocsp_date_arg == 'long':
1970 ocsp_date = minica.OCSP_DATE_LONG
dadrian4ccf51c2016-07-20 15:36:58 -07001971 else:
1972 raise testserver_base.OptionError('unknown OCSP date: ' +
1973 ocsp_date_arg)
1974 ocsp_dates.append(ocsp_date)
1975
1976 if len(ocsp_states) != len(ocsp_dates):
1977 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1978 'count')
1979
1980 ocsp_produced = None
1981 if self.options.ocsp_produced == 'valid':
1982 ocsp_produced = minica.OCSP_PRODUCED_VALID
1983 elif self.options.ocsp_produced == 'before':
1984 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1985 elif self.options.ocsp_produced == 'after':
1986 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
mattm@chromium.org830a3712012-11-07 23:00:07 +00001987 else:
dadrian4ccf51c2016-07-20 15:36:58 -07001988 raise testserver_base.OptionError('unknown OCSP produced: ' +
1989 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001990
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001991 ocsp_server_port = self.__ocsp_server.server_port
1992 if self.options.ocsp_proxy_port_number != 0:
1993 ocsp_server_port = self.options.ocsp_proxy_port_number
1994 server_data['ocsp_port'] = self.__ocsp_server.server_port
1995
mattm@chromium.org830a3712012-11-07 23:00:07 +00001996 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00001997 subject = self.options.cert_common_name,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001998 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07001999 ocsp_states = ocsp_states,
2000 ocsp_dates = ocsp_dates,
2001 ocsp_produced = ocsp_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002002 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002003
davidben3e2564a2014-11-07 18:51:00 -08002004 if self.options.ocsp_server_unavailable:
2005 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2006 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2007 else:
2008 self.__ocsp_server.ocsp_response = ocsp_der
mattm10ede842016-11-29 11:57:16 -08002009 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002010
2011 for ca_cert in self.options.ssl_client_ca:
2012 if not os.path.isfile(ca_cert):
2013 raise testserver_base.OptionError(
2014 'specified trusted client CA file not found: ' + ca_cert +
2015 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002016
2017 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002018 if self.options.staple_ocsp_response:
2019 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002020
mattm@chromium.org830a3712012-11-07 23:00:07 +00002021 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2022 self.options.ssl_client_auth,
2023 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002024 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002025 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002026 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002027 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002028 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002029 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002030 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002031 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002032 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002033 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002034 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002035 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002036 self.options.alert_after_handshake,
2037 self.options.disable_channel_id,
2038 self.options.disable_extended_master_secret,
2039 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002040 print 'HTTPS server started on https://%s:%d...' % \
2041 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002042 else:
2043 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002044 print 'HTTP server started on http://%s:%d...' % \
2045 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002046
2047 server.data_dir = self.__make_data_dir()
2048 server.file_root_url = self.options.file_root_url
2049 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 elif self.options.server_type == SERVER_WEBSOCKET:
2051 # Launch pywebsocket via WebSocketServer.
2052 logger = logging.getLogger()
2053 logger.addHandler(logging.StreamHandler())
2054 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2055 # is required to work correctly. It should be fixed from pywebsocket side.
2056 os.chdir(self.__make_data_dir())
2057 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002058 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002059 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002060 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002061 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002062 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2063 if not os.path.isfile(key_path):
2064 raise testserver_base.OptionError(
2065 'specified server cert file not found: ' +
2066 self.options.cert_and_key_file + ' exiting...')
2067 websocket_options.private_key = key_path
2068 websocket_options.certificate = key_path
2069
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002071 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002072 websocket_options.tls_client_auth = True
2073 if len(self.options.ssl_client_ca) != 1:
2074 raise testserver_base.OptionError(
2075 'one trusted client CA file should be specified')
2076 if not os.path.isfile(self.options.ssl_client_ca[0]):
2077 raise testserver_base.OptionError(
2078 'specified trusted client CA file not found: ' +
2079 self.options.ssl_client_ca[0] + ' exiting...')
2080 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002081 print 'Trying to start websocket server on %s://%s:%d...' % \
2082 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002083 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002084 print 'WebSocket server started on %s://%s:%d...' % \
2085 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002086 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002087 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088 elif self.options.server_type == SERVER_TCP_ECHO:
2089 # Used for generating the key (randomly) that encodes the "echo request"
2090 # message.
2091 random.seed()
2092 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002093 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002094 server_data['port'] = server.server_port
2095 elif self.options.server_type == SERVER_UDP_ECHO:
2096 # Used for generating the key (randomly) that encodes the "echo request"
2097 # message.
2098 random.seed()
2099 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002100 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002101 server_data['port'] = server.server_port
2102 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002103 BasicAuthProxyRequestHandler.redirect_connect_to_localhost = \
2104 self.options.redirect_connect_to_localhost
mattm@chromium.org830a3712012-11-07 23:00:07 +00002105 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002106 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002107 server_data['port'] = server.server_port
2108 elif self.options.server_type == SERVER_FTP:
2109 my_data_dir = self.__make_data_dir()
2110
2111 # Instantiate a dummy authorizer for managing 'virtual' users
2112 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2113
xleng9d4c45f2015-05-04 16:26:12 -07002114 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002115 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2116
xleng9d4c45f2015-05-04 16:26:12 -07002117 # Define a read-only anonymous user unless disabled
2118 if not self.options.no_anonymous_ftp_user:
2119 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002120
2121 # Instantiate FTP handler class
2122 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2123 ftp_handler.authorizer = authorizer
2124
2125 # Define a customized banner (string returned when client connects)
2126 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2127 pyftpdlib.ftpserver.__ver__)
2128
2129 # Instantiate FTP server class and listen to address:port
2130 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2131 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002132 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002133 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 raise testserver_base.OptionError('unknown server type' +
2135 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002136
mattm@chromium.org830a3712012-11-07 23:00:07 +00002137 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002138
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 def run_server(self):
2140 if self.__ocsp_server:
2141 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002142
mattm@chromium.org830a3712012-11-07 23:00:07 +00002143 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002144
mattm@chromium.org830a3712012-11-07 23:00:07 +00002145 if self.__ocsp_server:
2146 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002147
mattm@chromium.org830a3712012-11-07 23:00:07 +00002148 def add_options(self):
2149 testserver_base.TestServerRunner.add_options(self)
2150 self.option_parser.add_option('-f', '--ftp', action='store_const',
2151 const=SERVER_FTP, default=SERVER_HTTP,
2152 dest='server_type',
2153 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002154 self.option_parser.add_option('--tcp-echo', action='store_const',
2155 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2156 dest='server_type',
2157 help='start up a tcp echo server.')
2158 self.option_parser.add_option('--udp-echo', action='store_const',
2159 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2160 dest='server_type',
2161 help='start up a udp echo server.')
2162 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2163 const=SERVER_BASIC_AUTH_PROXY,
2164 default=SERVER_HTTP, dest='server_type',
2165 help='start up a proxy server which requires '
2166 'basic authentication.')
2167 self.option_parser.add_option('--websocket', action='store_const',
2168 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2169 dest='server_type',
2170 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002171 self.option_parser.add_option('--https', action='store_true',
2172 dest='https', help='Specify that https '
2173 'should be used.')
2174 self.option_parser.add_option('--cert-and-key-file',
2175 dest='cert_and_key_file', help='specify the '
2176 'path to the file containing the certificate '
2177 'and private key for the server in PEM '
2178 'format')
mattm10ede842016-11-29 11:57:16 -08002179 self.option_parser.add_option('--aia-intermediate', action='store_true',
2180 dest='aia_intermediate',
2181 help='generate a certificate chain that '
2182 'requires AIA cert fetching, and run a '
2183 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002184 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2185 help='The type of OCSP response generated '
2186 'for the automatically generated '
2187 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002188 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2189 default='valid', help='The validity of the '
2190 'range between thisUpdate and nextUpdate')
2191 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2192 default='valid', help='producedAt relative '
2193 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002194 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2195 default=0, type=int,
2196 help='If non-zero then the generated '
2197 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002198 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2199 default="127.0.0.1",
2200 help='The generated certificate will have '
2201 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002202 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2203 default='0', type='int',
2204 help='If nonzero, certain TLS connections '
2205 'will be aborted in order to test version '
2206 'fallback. 1 means all TLS versions will be '
2207 'aborted. 2 means TLS 1.1 or higher will be '
2208 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002209 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002210 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002211 self.option_parser.add_option('--tls-intolerance-type',
2212 dest='tls_intolerance_type',
2213 default="alert",
2214 help='Controls how the server reacts to a '
2215 'TLS version it is intolerant to. Valid '
2216 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002217 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2218 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002219 default='',
2220 help='Base64 encoded SCT list. If set, '
2221 'server will respond with a '
2222 'signed_certificate_timestamp TLS extension '
2223 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002224 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2225 default=False, const=True,
2226 action='store_const',
2227 help='If given, TLS_FALLBACK_SCSV support '
2228 'will be enabled. This causes the server to '
2229 'reject fallback connections from compatible '
2230 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002231 self.option_parser.add_option('--staple-ocsp-response',
2232 dest='staple_ocsp_response',
2233 default=False, action='store_true',
2234 help='If set, server will staple the OCSP '
2235 'response whenever OCSP is on and the client '
2236 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002237 self.option_parser.add_option('--https-record-resume',
2238 dest='record_resume', const=True,
2239 default=False, action='store_const',
2240 help='Record resumption cache events rather '
2241 'than resuming as normal. Allows the use of '
2242 'the /ssl-session-cache request')
2243 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2244 help='Require SSL client auth on every '
2245 'connection.')
2246 self.option_parser.add_option('--ssl-client-ca', action='append',
2247 default=[], help='Specify that the client '
2248 'certificate request should include the CA '
2249 'named in the subject of the DER-encoded '
2250 'certificate contained in the specified '
2251 'file. This option may appear multiple '
2252 'times, indicating multiple CA names should '
2253 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002254 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2255 default=[], help='Specify that the client '
2256 'certificate request should include the '
2257 'specified certificate_type value. This '
2258 'option may appear multiple times, '
2259 'indicating multiple values should be send '
2260 'in the request. Valid values are '
2261 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2262 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002263 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2264 help='Specify the bulk encryption '
2265 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002266 'SSL server. Valid values are "aes128gcm", '
2267 '"aes256", "aes128", "3des", "rc4". If '
2268 'omitted, all algorithms will be used. This '
2269 'option may appear multiple times, '
2270 'indicating multiple algorithms should be '
2271 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002272 self.option_parser.add_option('--ssl-key-exchange', action='append',
2273 help='Specify the key exchange algorithm(s)'
2274 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002275 'Valid values are "rsa", "dhe_rsa", '
2276 '"ecdhe_rsa". If omitted, all algorithms '
2277 'will be used. This option may appear '
2278 'multiple times, indicating multiple '
2279 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002280 self.option_parser.add_option('--alpn-protocols', action='append',
2281 help='Specify the list of ALPN protocols. '
2282 'The server will not send an ALPN response '
2283 'if this list does not overlap with the '
2284 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002285 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002286 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002287 'an NPN response. The server will not'
2288 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002289 self.option_parser.add_option('--file-root-url', default='/files/',
2290 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002291 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2292 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2293 dest='ws_basic_auth',
2294 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002295 self.option_parser.add_option('--ocsp-server-unavailable',
2296 dest='ocsp_server_unavailable',
2297 default=False, action='store_true',
2298 help='If set, the OCSP server will return '
2299 'a tryLater status rather than the actual '
2300 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002301 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2302 type='int', dest='ocsp_proxy_port_number',
2303 help='Port allocated for OCSP proxy '
2304 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002305 self.option_parser.add_option('--alert-after-handshake',
2306 dest='alert_after_handshake',
2307 default=False, action='store_true',
2308 help='If set, the server will send a fatal '
2309 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002310 self.option_parser.add_option('--no-anonymous-ftp-user',
2311 dest='no_anonymous_ftp_user',
2312 default=False, action='store_true',
2313 help='If set, the FTP server will not create '
2314 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002315 self.option_parser.add_option('--disable-channel-id', action='store_true')
2316 self.option_parser.add_option('--disable-extended-master-secret',
2317 action='store_true')
2318 self.option_parser.add_option('--token-binding-params', action='append',
2319 default=[], type='int')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002320 self.option_parser.add_option('--redirect-connect-to-localhost',
2321 dest='redirect_connect_to_localhost',
2322 default=False, action='store_true',
2323 help='If set, the Proxy server will connect '
2324 'to localhost instead of the requested URL '
2325 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002326
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002327
initial.commit94958cf2008-07-26 22:42:52 +00002328if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002329 sys.exit(ServerRunner().main())