blob: 92ca87cc703056e87bcc3520de703467d94b62ba [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
Adam Rice34b2e312018-04-06 16:48:30 +0000127class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
128 HTTPServer):
129 """This variant of HTTPServer creates a new thread for every connection. It
130 should only be used with handlers that are known to be threadsafe."""
131
132 pass
133
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000134class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
135 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000136 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000137 """This is a specialization of HTTPServer that serves an
138 OCSP response"""
139
140 def serve_forever_on_thread(self):
141 self.thread = threading.Thread(target = self.serve_forever,
142 name = "OCSPServerThread")
143 self.thread.start()
144
145 def stop_serving(self):
146 self.shutdown()
147 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148
mattm@chromium.org830a3712012-11-07 23:00:07 +0000149
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000150class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000151 testserver_base.ClientRestrictingServerMixIn,
152 testserver_base.BrokenPipeHandlerMixIn,
153 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000154 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000155 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000156
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000157 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000158 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700159 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
160 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000161 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700162 fallback_scsv_enabled, ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -0700163 alert_after_handshake, disable_channel_id, disable_ems,
164 token_binding_params):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000165 self.cert_chain = tlslite.api.X509CertChain()
166 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000167 # Force using only python implementation - otherwise behavior is different
168 # depending on whether m2crypto Python module is present (error is thrown
169 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
170 # the hood.
171 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
172 private=True,
173 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000174 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000175 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000176 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700177 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000178 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000179 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000180 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000181
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000182 if ssl_client_auth:
183 for ca_file in ssl_client_cas:
184 s = open(ca_file).read()
185 x509 = tlslite.api.X509()
186 x509.parse(s)
187 self.ssl_client_cas.append(x509.subject)
188
189 for cert_type in ssl_client_cert_types:
190 self.ssl_client_cert_types.append({
191 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800196 # Enable SSLv3 for testing purposes.
197 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000202 if tls_intolerant != 0:
203 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
204 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700205 if alert_after_handshake:
206 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700207 if disable_channel_id:
208 self.ssl_handshake_settings.enableChannelID = False
209 if disable_ems:
210 self.ssl_handshake_settings.enableExtendedMasterSecret = False
211 self.ssl_handshake_settings.supportedTokenBindingParams = \
212 token_binding_params
bnc5fb33bd2016-08-05 12:09:21 -0700213 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000214
rsleevi8146efa2015-03-16 12:31:24 -0700215 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000216 # If record_resume_info is true then we'll replace the session cache with
217 # an object that records the lookups and inserts that it sees.
218 self.session_cache = RecordingSSLSessionCache()
219 else:
220 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000221 testserver_base.StoppableHTTPServer.__init__(self,
222 server_address,
223 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000224
225 def handshake(self, tlsConnection):
226 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000227
initial.commit94958cf2008-07-26 22:42:52 +0000228 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000229 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000230 tlsConnection.handshakeServer(certChain=self.cert_chain,
231 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000232 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000233 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000234 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000235 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000236 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700237 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000238 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000239 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000240 fallbackSCSV=self.fallback_scsv_enabled,
241 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000242 tlsConnection.ignoreAbruptClose = True
243 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000244 except tlslite.api.TLSAbruptCloseError:
245 # Ignore abrupt close.
246 return True
initial.commit94958cf2008-07-26 22:42:52 +0000247 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000248 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000249 return False
250
akalin@chromium.org154bb132010-11-12 02:20:27 +0000251
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000252class FTPServer(testserver_base.ClientRestrictingServerMixIn,
253 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000254 """This is a specialization of FTPServer that adds client verification."""
255
256 pass
257
258
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000259class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
260 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000261 """A TCP echo server that echoes back what it has received."""
262
263 def server_bind(self):
264 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000265
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000266 SocketServer.TCPServer.server_bind(self)
267 host, port = self.socket.getsockname()[:2]
268 self.server_name = socket.getfqdn(host)
269 self.server_port = port
270
271 def serve_forever(self):
272 self.stop = False
273 self.nonce_time = None
274 while not self.stop:
275 self.handle_request()
276 self.socket.close()
277
278
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000279class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
280 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000281 """A UDP echo server that echoes back what it has received."""
282
283 def server_bind(self):
284 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000285
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000286 SocketServer.UDPServer.server_bind(self)
287 host, port = self.socket.getsockname()[:2]
288 self.server_name = socket.getfqdn(host)
289 self.server_port = port
290
291 def serve_forever(self):
292 self.stop = False
293 self.nonce_time = None
294 while not self.stop:
295 self.handle_request()
296 self.socket.close()
297
298
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000299class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000300 # Class variables to allow for persistence state between page handler
301 # invocations
302 rst_limits = {}
303 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000304
305 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000306 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000307 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000308 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000309 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000310 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000311 self.NoCacheMaxAgeTimeHandler,
312 self.NoCacheTimeHandler,
313 self.CacheTimeHandler,
314 self.CacheExpiresHandler,
315 self.CacheProxyRevalidateHandler,
316 self.CachePrivateHandler,
317 self.CachePublicHandler,
318 self.CacheSMaxAgeHandler,
319 self.CacheMustRevalidateHandler,
320 self.CacheMustRevalidateMaxAgeHandler,
321 self.CacheNoStoreHandler,
322 self.CacheNoStoreMaxAgeHandler,
323 self.CacheNoTransformHandler,
324 self.DownloadHandler,
325 self.DownloadFinishHandler,
326 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000327 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000328 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000329 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000331 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000332 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000333 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000334 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.AuthBasicHandler,
336 self.AuthDigestHandler,
337 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000338 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000339 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700341 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000343 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000344 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000345 self.GetChannelID,
nharper08eae822016-01-25 15:54:14 -0800346 self.GetTokenBindingEKM,
nharpercb1adc32016-03-30 16:05:48 -0700347 self.ForwardTokenBindingHeader,
pneubeckfd4f0442015-08-07 04:55:10 -0700348 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700349 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000350 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000351 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000352 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000353 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000354 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000355 self.PostOnlyFileHandler,
356 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000357 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000358 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000359 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000360 head_handlers = [
361 self.FileHandler,
362 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000363
maruel@google.come250a9b2009-03-10 17:39:46 +0000364 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000365 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000366 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000367 'gif': 'image/gif',
368 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000369 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700370 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000371 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000372 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000373 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000374 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000375 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000376 }
initial.commit94958cf2008-07-26 22:42:52 +0000377 self._default_mime_type = 'text/html'
378
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000379 testserver_base.BasePageHandler.__init__(self, request, client_address,
380 socket_server, connect_handlers,
381 get_handlers, head_handlers,
382 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000383
initial.commit94958cf2008-07-26 22:42:52 +0000384 def GetMIMETypeFromName(self, file_name):
385 """Returns the mime type for the specified file_name. So far it only looks
386 at the file extension."""
387
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000388 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000389 if len(extension) == 0:
390 # no extension.
391 return self._default_mime_type
392
ericroman@google.comc17ca532009-05-07 03:51:05 +0000393 # extension starts with a dot, so we need to remove it
394 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000395
initial.commit94958cf2008-07-26 22:42:52 +0000396 def NoCacheMaxAgeTimeHandler(self):
397 """This request handler yields a page with the title set to the current
398 system time, and no caching requested."""
399
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000400 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000401 return False
402
403 self.send_response(200)
404 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000405 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000406 self.end_headers()
407
maruel@google.come250a9b2009-03-10 17:39:46 +0000408 self.wfile.write('<html><head><title>%s</title></head></html>' %
409 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000410
411 return True
412
413 def NoCacheTimeHandler(self):
414 """This request handler yields a page with the title set to the current
415 system time, and no caching requested."""
416
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000417 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000418 return False
419
420 self.send_response(200)
421 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000422 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000423 self.end_headers()
424
maruel@google.come250a9b2009-03-10 17:39:46 +0000425 self.wfile.write('<html><head><title>%s</title></head></html>' %
426 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000427
428 return True
429
430 def CacheTimeHandler(self):
431 """This request handler yields a page with the title set to the current
432 system time, and allows caching for one minute."""
433
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000434 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000435 return False
436
437 self.send_response(200)
438 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000439 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000440 self.end_headers()
441
maruel@google.come250a9b2009-03-10 17:39:46 +0000442 self.wfile.write('<html><head><title>%s</title></head></html>' %
443 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000444
445 return True
446
447 def CacheExpiresHandler(self):
448 """This request handler yields a page with the title set to the current
449 system time, and set the page to expire on 1 Jan 2099."""
450
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000451 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000452 return False
453
454 self.send_response(200)
455 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000456 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000457 self.end_headers()
458
maruel@google.come250a9b2009-03-10 17:39:46 +0000459 self.wfile.write('<html><head><title>%s</title></head></html>' %
460 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000461
462 return True
463
464 def CacheProxyRevalidateHandler(self):
465 """This request handler yields a page with the title set to the current
466 system time, and allows caching for 60 seconds"""
467
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000468 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000469 return False
470
471 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000472 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000473 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
474 self.end_headers()
475
maruel@google.come250a9b2009-03-10 17:39:46 +0000476 self.wfile.write('<html><head><title>%s</title></head></html>' %
477 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000478
479 return True
480
481 def CachePrivateHandler(self):
482 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700483 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000484
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000485 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000486 return False
487
488 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000489 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000490 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000491 self.end_headers()
492
maruel@google.come250a9b2009-03-10 17:39:46 +0000493 self.wfile.write('<html><head><title>%s</title></head></html>' %
494 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000495
496 return True
497
498 def CachePublicHandler(self):
499 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700500 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000501
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000502 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000503 return False
504
505 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000506 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000507 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000508 self.end_headers()
509
maruel@google.come250a9b2009-03-10 17:39:46 +0000510 self.wfile.write('<html><head><title>%s</title></head></html>' %
511 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000512
513 return True
514
515 def CacheSMaxAgeHandler(self):
516 """This request handler yields a page with the title set to the current
517 system time, and does not allow for caching."""
518
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000519 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000520 return False
521
522 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000523 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000524 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
525 self.end_headers()
526
maruel@google.come250a9b2009-03-10 17:39:46 +0000527 self.wfile.write('<html><head><title>%s</title></head></html>' %
528 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000529
530 return True
531
532 def CacheMustRevalidateHandler(self):
533 """This request handler yields a page with the title set to the current
534 system time, and does not allow caching."""
535
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000536 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000537 return False
538
539 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000540 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000541 self.send_header('Cache-Control', 'must-revalidate')
542 self.end_headers()
543
maruel@google.come250a9b2009-03-10 17:39:46 +0000544 self.wfile.write('<html><head><title>%s</title></head></html>' %
545 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000546
547 return True
548
549 def CacheMustRevalidateMaxAgeHandler(self):
550 """This request handler yields a page with the title set to the current
551 system time, and does not allow caching event though max-age of 60
552 seconds is specified."""
553
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000554 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000555 return False
556
557 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000558 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000559 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
560 self.end_headers()
561
maruel@google.come250a9b2009-03-10 17:39:46 +0000562 self.wfile.write('<html><head><title>%s</title></head></html>' %
563 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000564
565 return True
566
initial.commit94958cf2008-07-26 22:42:52 +0000567 def CacheNoStoreHandler(self):
568 """This request handler yields a page with the title set to the current
569 system time, and does not allow the page to be stored."""
570
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000571 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000572 return False
573
574 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000575 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000576 self.send_header('Cache-Control', 'no-store')
577 self.end_headers()
578
maruel@google.come250a9b2009-03-10 17:39:46 +0000579 self.wfile.write('<html><head><title>%s</title></head></html>' %
580 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000581
582 return True
583
584 def CacheNoStoreMaxAgeHandler(self):
585 """This request handler yields a page with the title set to the current
586 system time, and does not allow the page to be stored even though max-age
587 of 60 seconds is specified."""
588
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000589 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000590 return False
591
592 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000593 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000594 self.send_header('Cache-Control', 'max-age=60, no-store')
595 self.end_headers()
596
maruel@google.come250a9b2009-03-10 17:39:46 +0000597 self.wfile.write('<html><head><title>%s</title></head></html>' %
598 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000599
600 return True
601
602
603 def CacheNoTransformHandler(self):
604 """This request handler yields a page with the title set to the current
605 system time, and does not allow the content to transformed during
606 user-agent caching"""
607
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000608 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000609 return False
610
611 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000612 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000613 self.send_header('Cache-Control', 'no-transform')
614 self.end_headers()
615
maruel@google.come250a9b2009-03-10 17:39:46 +0000616 self.wfile.write('<html><head><title>%s</title></head></html>' %
617 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000618
619 return True
620
621 def EchoHeader(self):
622 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000623
ananta@chromium.org219b2062009-10-23 16:09:41 +0000624 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000625
ananta@chromium.org56812d02011-04-07 17:52:05 +0000626 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000627 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000628 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000629
ananta@chromium.org56812d02011-04-07 17:52:05 +0000630 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000631
632 def EchoHeaderHelper(self, echo_header):
633 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000634
ananta@chromium.org219b2062009-10-23 16:09:41 +0000635 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000636 return False
637
638 query_char = self.path.find('?')
639 if query_char != -1:
640 header_name = self.path[query_char+1:]
641
642 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000643 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000644 if echo_header == '/echoheadercache':
645 self.send_header('Cache-control', 'max-age=60000')
646 else:
647 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000648 # insert a vary header to properly indicate that the cachability of this
649 # request is subject to value of the request header being echoed.
650 if len(header_name) > 0:
651 self.send_header('Vary', header_name)
652 self.end_headers()
653
654 if len(header_name) > 0:
655 self.wfile.write(self.headers.getheader(header_name))
656
657 return True
658
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000659 def ReadRequestBody(self):
660 """This function reads the body of the current HTTP request, handling
661 both plain and chunked transfer encoded requests."""
662
663 if self.headers.getheader('transfer-encoding') != 'chunked':
664 length = int(self.headers.getheader('content-length'))
665 return self.rfile.read(length)
666
667 # Read the request body as chunks.
668 body = ""
669 while True:
670 line = self.rfile.readline()
671 length = int(line, 16)
672 if length == 0:
673 self.rfile.readline()
674 break
675 body += self.rfile.read(length)
676 self.rfile.read(2)
677 return body
678
initial.commit94958cf2008-07-26 22:42:52 +0000679 def EchoHandler(self):
680 """This handler just echoes back the payload of the request, for testing
681 form submission."""
682
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000683 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000684 return False
685
hirono2838c572015-01-21 12:18:11 -0800686 _, _, _, _, query, _ = urlparse.urlparse(self.path)
687 query_params = cgi.parse_qs(query, True)
688 if 'status' in query_params:
689 self.send_response(int(query_params['status'][0]))
690 else:
691 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000692 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000693 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000694 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000695 return True
696
697 def EchoTitleHandler(self):
698 """This handler is like Echo, but sets the page title to the request."""
699
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000700 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000701 return False
702
703 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000704 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000705 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000706 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000707 self.wfile.write('<html><head><title>')
708 self.wfile.write(request)
709 self.wfile.write('</title></head></html>')
710 return True
711
712 def EchoAllHandler(self):
713 """This handler yields a (more) human-readable page listing information
714 about the request header & contents."""
715
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000716 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000717 return False
718
719 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000720 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000721 self.end_headers()
722 self.wfile.write('<html><head><style>'
723 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
724 '</style></head><body>'
725 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000726 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000727 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000728
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000729 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000730 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000731 params = cgi.parse_qs(qs, keep_blank_values=1)
732
733 for param in params:
734 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000735
736 self.wfile.write('</pre>')
737
738 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
739
740 self.wfile.write('</body></html>')
741 return True
742
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000743 def EchoMultipartPostHandler(self):
744 """This handler echoes received multipart post data as json format."""
745
746 if not (self._ShouldHandleRequest("/echomultipartpost") or
747 self._ShouldHandleRequest("/searchbyimage")):
748 return False
749
750 content_type, parameters = cgi.parse_header(
751 self.headers.getheader('content-type'))
752 if content_type == 'multipart/form-data':
753 post_multipart = cgi.parse_multipart(self.rfile, parameters)
754 elif content_type == 'application/x-www-form-urlencoded':
755 raise Exception('POST by application/x-www-form-urlencoded is '
756 'not implemented.')
757 else:
758 post_multipart = {}
759
760 # Since the data can be binary, we encode them by base64.
761 post_multipart_base64_encoded = {}
762 for field, values in post_multipart.items():
763 post_multipart_base64_encoded[field] = [base64.b64encode(value)
764 for value in values]
765
766 result = {'POST_multipart' : post_multipart_base64_encoded}
767
768 self.send_response(200)
769 self.send_header("Content-type", "text/plain")
770 self.end_headers()
771 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
772 return True
773
initial.commit94958cf2008-07-26 22:42:52 +0000774 def DownloadHandler(self):
775 """This handler sends a downloadable file with or without reporting
776 the size (6K)."""
777
778 if self.path.startswith("/download-unknown-size"):
779 send_length = False
780 elif self.path.startswith("/download-known-size"):
781 send_length = True
782 else:
783 return False
784
785 #
786 # The test which uses this functionality is attempting to send
787 # small chunks of data to the client. Use a fairly large buffer
788 # so that we'll fill chrome's IO buffer enough to force it to
789 # actually write the data.
790 # See also the comments in the client-side of this test in
791 # download_uitest.cc
792 #
793 size_chunk1 = 35*1024
794 size_chunk2 = 10*1024
795
796 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000797 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000798 self.send_header('Cache-Control', 'max-age=0')
799 if send_length:
800 self.send_header('Content-Length', size_chunk1 + size_chunk2)
801 self.end_headers()
802
803 # First chunk of data:
804 self.wfile.write("*" * size_chunk1)
805 self.wfile.flush()
806
807 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000808 self.server.wait_for_download = True
809 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.server.handle_request()
811
812 # Second chunk of data:
813 self.wfile.write("*" * size_chunk2)
814 return True
815
816 def DownloadFinishHandler(self):
817 """This handler just tells the server to finish the current download."""
818
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000819 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000820 return False
821
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000822 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000823 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000824 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000825 self.send_header('Cache-Control', 'max-age=0')
826 self.end_headers()
827 return True
828
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000829 def _ReplaceFileData(self, data, query_parameters):
830 """Replaces matching substrings in a file.
831
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000832 If the 'replace_text' URL query parameter is present, it is expected to be
833 of the form old_text:new_text, which indicates that any old_text strings in
834 the file are replaced with new_text. Multiple 'replace_text' parameters may
835 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000836
837 If the parameters are not present, |data| is returned.
838 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000839
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000840 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000841 replace_text_values = query_dict.get('replace_text', [])
842 for replace_text_value in replace_text_values:
843 replace_text_args = replace_text_value.split(':')
844 if len(replace_text_args) != 2:
845 raise ValueError(
846 'replace_text must be of form old_text:new_text. Actual value: %s' %
847 replace_text_value)
848 old_text_b64, new_text_b64 = replace_text_args
849 old_text = base64.urlsafe_b64decode(old_text_b64)
850 new_text = base64.urlsafe_b64decode(new_text_b64)
851 data = data.replace(old_text, new_text)
852 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000853
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000854 def ZipFileHandler(self):
855 """This handler sends the contents of the requested file in compressed form.
856 Can pass in a parameter that specifies that the content length be
857 C - the compressed size (OK),
858 U - the uncompressed size (Non-standard, but handled),
859 S - less than compressed (OK because we keep going),
860 M - larger than compressed but less than uncompressed (an error),
861 L - larger than uncompressed (an error)
862 Example: compressedfiles/Picture_1.doc?C
863 """
864
865 prefix = "/compressedfiles/"
866 if not self.path.startswith(prefix):
867 return False
868
869 # Consume a request body if present.
870 if self.command == 'POST' or self.command == 'PUT' :
871 self.ReadRequestBody()
872
873 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
874
875 if not query in ('C', 'U', 'S', 'M', 'L'):
876 return False
877
878 sub_path = url_path[len(prefix):]
879 entries = sub_path.split('/')
880 file_path = os.path.join(self.server.data_dir, *entries)
881 if os.path.isdir(file_path):
882 file_path = os.path.join(file_path, 'index.html')
883
884 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000885 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000886 self.send_error(404)
887 return True
888
889 f = open(file_path, "rb")
890 data = f.read()
891 uncompressed_len = len(data)
892 f.close()
893
894 # Compress the data.
895 data = zlib.compress(data)
896 compressed_len = len(data)
897
898 content_length = compressed_len
899 if query == 'U':
900 content_length = uncompressed_len
901 elif query == 'S':
902 content_length = compressed_len / 2
903 elif query == 'M':
904 content_length = (compressed_len + uncompressed_len) / 2
905 elif query == 'L':
906 content_length = compressed_len + uncompressed_len
907
908 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000909 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000910 self.send_header('Content-encoding', 'deflate')
911 self.send_header('Connection', 'close')
912 self.send_header('Content-Length', content_length)
913 self.send_header('ETag', '\'' + file_path + '\'')
914 self.end_headers()
915
916 self.wfile.write(data)
917
918 return True
919
initial.commit94958cf2008-07-26 22:42:52 +0000920 def FileHandler(self):
921 """This handler sends the contents of the requested file. Wow, it's like
922 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000923
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000924 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000925 if not self.path.startswith(prefix):
926 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000927 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000928
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000929 def PostOnlyFileHandler(self):
930 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000931
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000932 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000933 if not self.path.startswith(prefix):
934 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000935 return self._FileHandlerHelper(prefix)
936
937 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000938 request_body = ''
939 if self.command == 'POST' or self.command == 'PUT':
940 # Consume a request body if present.
941 request_body = self.ReadRequestBody()
942
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000943 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000944 query_dict = cgi.parse_qs(query)
945
946 expected_body = query_dict.get('expected_body', [])
947 if expected_body and request_body not in expected_body:
948 self.send_response(404)
949 self.end_headers()
950 self.wfile.write('')
951 return True
952
953 expected_headers = query_dict.get('expected_headers', [])
954 for expected_header in expected_headers:
955 header_name, expected_value = expected_header.split(':')
956 if self.headers.getheader(header_name) != expected_value:
957 self.send_response(404)
958 self.end_headers()
959 self.wfile.write('')
960 return True
961
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000962 sub_path = url_path[len(prefix):]
963 entries = sub_path.split('/')
964 file_path = os.path.join(self.server.data_dir, *entries)
965 if os.path.isdir(file_path):
966 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000967
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000968 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000969 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000970 self.send_error(404)
971 return True
972
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000973 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000974 data = f.read()
975 f.close()
976
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000977 data = self._ReplaceFileData(data, query)
978
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000979 old_protocol_version = self.protocol_version
980
initial.commit94958cf2008-07-26 22:42:52 +0000981 # If file.mock-http-headers exists, it contains the headers we
982 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000983 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000984 if os.path.isfile(headers_path):
985 f = open(headers_path, "r")
986
987 # "HTTP/1.1 200 OK"
988 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000989 http_major, http_minor, status_code = re.findall(
990 'HTTP/(\d+).(\d+) (\d+)', response)[0]
991 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000992 self.send_response(int(status_code))
993
994 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000995 header_values = re.findall('(\S+):\s*(.*)', line)
996 if len(header_values) > 0:
997 # "name: value"
998 name, value = header_values[0]
999 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001000 f.close()
1001 else:
1002 # Could be more generic once we support mime-type sniffing, but for
1003 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001004
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001005 range_header = self.headers.get('Range')
1006 if range_header and range_header.startswith('bytes='):
1007 # Note this doesn't handle all valid byte range_header values (i.e.
1008 # left open ended ones), just enough for what we needed so far.
1009 range_header = range_header[6:].split('-')
1010 start = int(range_header[0])
1011 if range_header[1]:
1012 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001013 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001014 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001015
1016 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001017 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1018 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001019 self.send_header('Content-Range', content_range)
1020 data = data[start: end + 1]
1021 else:
1022 self.send_response(200)
1023
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001024 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001025 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001026 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001027 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001028 self.end_headers()
1029
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001030 if (self.command != 'HEAD'):
1031 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001032
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001033 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001034 return True
1035
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001036 def SetCookieHandler(self):
1037 """This handler just sets a cookie, for testing cookie handling."""
1038
1039 if not self._ShouldHandleRequest("/set-cookie"):
1040 return False
1041
1042 query_char = self.path.find('?')
1043 if query_char != -1:
1044 cookie_values = self.path[query_char + 1:].split('&')
1045 else:
1046 cookie_values = ("",)
1047 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001048 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001049 for cookie_value in cookie_values:
1050 self.send_header('Set-Cookie', '%s' % cookie_value)
1051 self.end_headers()
1052 for cookie_value in cookie_values:
1053 self.wfile.write('%s' % cookie_value)
1054 return True
1055
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001056 def SetManyCookiesHandler(self):
1057 """This handler just sets a given number of cookies, for testing handling
1058 of large numbers of cookies."""
1059
1060 if not self._ShouldHandleRequest("/set-many-cookies"):
1061 return False
1062
1063 query_char = self.path.find('?')
1064 if query_char != -1:
1065 num_cookies = int(self.path[query_char + 1:])
1066 else:
1067 num_cookies = 0
1068 self.send_response(200)
1069 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001070 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001071 self.send_header('Set-Cookie', 'a=')
1072 self.end_headers()
1073 self.wfile.write('%d cookies were sent' % num_cookies)
1074 return True
1075
mattm@chromium.org983fc462012-06-30 00:52:08 +00001076 def ExpectAndSetCookieHandler(self):
1077 """Expects some cookies to be sent, and if they are, sets more cookies.
1078
1079 The expect parameter specifies a required cookie. May be specified multiple
1080 times.
1081 The set parameter specifies a cookie to set if all required cookies are
1082 preset. May be specified multiple times.
1083 The data parameter specifies the response body data to be returned."""
1084
1085 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1086 return False
1087
1088 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1089 query_dict = cgi.parse_qs(query)
1090 cookies = set()
1091 if 'Cookie' in self.headers:
1092 cookie_header = self.headers.getheader('Cookie')
1093 cookies.update([s.strip() for s in cookie_header.split(';')])
1094 got_all_expected_cookies = True
1095 for expected_cookie in query_dict.get('expect', []):
1096 if expected_cookie not in cookies:
1097 got_all_expected_cookies = False
1098 self.send_response(200)
1099 self.send_header('Content-Type', 'text/html')
1100 if got_all_expected_cookies:
1101 for cookie_value in query_dict.get('set', []):
1102 self.send_header('Set-Cookie', '%s' % cookie_value)
1103 self.end_headers()
1104 for data_value in query_dict.get('data', []):
1105 self.wfile.write(data_value)
1106 return True
1107
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001108 def SetHeaderHandler(self):
1109 """This handler sets a response header. Parameters are in the
1110 key%3A%20value&key2%3A%20value2 format."""
1111
1112 if not self._ShouldHandleRequest("/set-header"):
1113 return False
1114
1115 query_char = self.path.find('?')
1116 if query_char != -1:
1117 headers_values = self.path[query_char + 1:].split('&')
1118 else:
1119 headers_values = ("",)
1120 self.send_response(200)
1121 self.send_header('Content-Type', 'text/html')
1122 for header_value in headers_values:
1123 header_value = urllib.unquote(header_value)
1124 (key, value) = header_value.split(': ', 1)
1125 self.send_header(key, value)
1126 self.end_headers()
1127 for header_value in headers_values:
1128 self.wfile.write('%s' % header_value)
1129 return True
1130
initial.commit94958cf2008-07-26 22:42:52 +00001131 def AuthBasicHandler(self):
1132 """This handler tests 'Basic' authentication. It just sends a page with
1133 title 'user/pass' if you succeed."""
1134
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001135 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001136 return False
1137
1138 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001139 expected_password = 'secret'
1140 realm = 'testrealm'
1141 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001142
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001143 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1144 query_params = cgi.parse_qs(query, True)
1145 if 'set-cookie-if-challenged' in query_params:
1146 set_cookie_if_challenged = True
1147 if 'password' in query_params:
1148 expected_password = query_params['password'][0]
1149 if 'realm' in query_params:
1150 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001151
initial.commit94958cf2008-07-26 22:42:52 +00001152 auth = self.headers.getheader('authorization')
1153 try:
1154 if not auth:
1155 raise Exception('no auth')
1156 b64str = re.findall(r'Basic (\S+)', auth)[0]
1157 userpass = base64.b64decode(b64str)
1158 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001159 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001160 raise Exception('wrong password')
1161 except Exception, e:
1162 # Authentication failed.
1163 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001164 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001165 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001166 if set_cookie_if_challenged:
1167 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001168 self.end_headers()
1169 self.wfile.write('<html><head>')
1170 self.wfile.write('<title>Denied: %s</title>' % e)
1171 self.wfile.write('</head><body>')
1172 self.wfile.write('auth=%s<p>' % auth)
1173 self.wfile.write('b64str=%s<p>' % b64str)
1174 self.wfile.write('username: %s<p>' % username)
1175 self.wfile.write('userpass: %s<p>' % userpass)
1176 self.wfile.write('password: %s<p>' % password)
1177 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1178 self.wfile.write('</body></html>')
1179 return True
1180
1181 # Authentication successful. (Return a cachable response to allow for
1182 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001183 old_protocol_version = self.protocol_version
1184 self.protocol_version = "HTTP/1.1"
1185
initial.commit94958cf2008-07-26 22:42:52 +00001186 if_none_match = self.headers.getheader('if-none-match')
1187 if if_none_match == "abc":
1188 self.send_response(304)
1189 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001190 elif url_path.endswith(".gif"):
1191 # Using chrome/test/data/google/logo.gif as the test image
1192 test_image_path = ['google', 'logo.gif']
1193 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1194 if not os.path.isfile(gif_path):
1195 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001196 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001197 return True
1198
1199 f = open(gif_path, "rb")
1200 data = f.read()
1201 f.close()
1202
1203 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001204 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001205 self.send_header('Cache-control', 'max-age=60000')
1206 self.send_header('Etag', 'abc')
1207 self.end_headers()
1208 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001209 else:
1210 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001211 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001212 self.send_header('Cache-control', 'max-age=60000')
1213 self.send_header('Etag', 'abc')
1214 self.end_headers()
1215 self.wfile.write('<html><head>')
1216 self.wfile.write('<title>%s/%s</title>' % (username, password))
1217 self.wfile.write('</head><body>')
1218 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001219 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001220 self.wfile.write('</body></html>')
1221
rvargas@google.com54453b72011-05-19 01:11:11 +00001222 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001223 return True
1224
tonyg@chromium.org75054202010-03-31 22:06:10 +00001225 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001226 """Returns a nonce that's stable per request path for the server's lifetime.
1227 This is a fake implementation. A real implementation would only use a given
1228 nonce a single time (hence the name n-once). However, for the purposes of
1229 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001230
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001231 Args:
1232 force_reset: Iff set, the nonce will be changed. Useful for testing the
1233 "stale" response.
1234 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001235
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001236 if force_reset or not self.server.nonce_time:
1237 self.server.nonce_time = time.time()
1238 return hashlib.md5('privatekey%s%d' %
1239 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001240
1241 def AuthDigestHandler(self):
1242 """This handler tests 'Digest' authentication.
1243
1244 It just sends a page with title 'user/pass' if you succeed.
1245
1246 A stale response is sent iff "stale" is present in the request path.
1247 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001248
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001249 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001250 return False
1251
tonyg@chromium.org75054202010-03-31 22:06:10 +00001252 stale = 'stale' in self.path
1253 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001254 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001255 password = 'secret'
1256 realm = 'testrealm'
1257
1258 auth = self.headers.getheader('authorization')
1259 pairs = {}
1260 try:
1261 if not auth:
1262 raise Exception('no auth')
1263 if not auth.startswith('Digest'):
1264 raise Exception('not digest')
1265 # Pull out all the name="value" pairs as a dictionary.
1266 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1267
1268 # Make sure it's all valid.
1269 if pairs['nonce'] != nonce:
1270 raise Exception('wrong nonce')
1271 if pairs['opaque'] != opaque:
1272 raise Exception('wrong opaque')
1273
1274 # Check the 'response' value and make sure it matches our magic hash.
1275 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001276 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001277 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001278 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001279 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001280 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001281 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1282 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001283 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001284
1285 if pairs['response'] != response:
1286 raise Exception('wrong password')
1287 except Exception, e:
1288 # Authentication failed.
1289 self.send_response(401)
1290 hdr = ('Digest '
1291 'realm="%s", '
1292 'domain="/", '
1293 'qop="auth", '
1294 'algorithm=MD5, '
1295 'nonce="%s", '
1296 'opaque="%s"') % (realm, nonce, opaque)
1297 if stale:
1298 hdr += ', stale="TRUE"'
1299 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001300 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001301 self.end_headers()
1302 self.wfile.write('<html><head>')
1303 self.wfile.write('<title>Denied: %s</title>' % e)
1304 self.wfile.write('</head><body>')
1305 self.wfile.write('auth=%s<p>' % auth)
1306 self.wfile.write('pairs=%s<p>' % pairs)
1307 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1308 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1309 self.wfile.write('</body></html>')
1310 return True
1311
1312 # Authentication successful.
1313 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001314 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001315 self.end_headers()
1316 self.wfile.write('<html><head>')
1317 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1318 self.wfile.write('</head><body>')
1319 self.wfile.write('auth=%s<p>' % auth)
1320 self.wfile.write('pairs=%s<p>' % pairs)
1321 self.wfile.write('</body></html>')
1322
1323 return True
1324
1325 def SlowServerHandler(self):
1326 """Wait for the user suggested time before responding. The syntax is
1327 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001328
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001329 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001330 return False
1331 query_char = self.path.find('?')
1332 wait_sec = 1.0
1333 if query_char >= 0:
1334 try:
davidben05f82202015-03-31 13:48:07 -07001335 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001336 except ValueError:
1337 pass
1338 time.sleep(wait_sec)
1339 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001340 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001341 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001342 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001343 return True
1344
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001345 def ChunkedServerHandler(self):
1346 """Send chunked response. Allows to specify chunks parameters:
1347 - waitBeforeHeaders - ms to wait before sending headers
1348 - waitBetweenChunks - ms to wait between chunks
1349 - chunkSize - size of each chunk in bytes
1350 - chunksNumber - number of chunks
1351 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1352 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001353
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001354 if not self._ShouldHandleRequest("/chunked"):
1355 return False
1356 query_char = self.path.find('?')
1357 chunkedSettings = {'waitBeforeHeaders' : 0,
1358 'waitBetweenChunks' : 0,
1359 'chunkSize' : 5,
1360 'chunksNumber' : 5}
1361 if query_char >= 0:
1362 params = self.path[query_char + 1:].split('&')
1363 for param in params:
1364 keyValue = param.split('=')
1365 if len(keyValue) == 2:
1366 try:
1367 chunkedSettings[keyValue[0]] = int(keyValue[1])
1368 except ValueError:
1369 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001370 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001371 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1372 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001373 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001374 self.send_header('Connection', 'close')
1375 self.send_header('Transfer-Encoding', 'chunked')
1376 self.end_headers()
1377 # Chunked encoding: sending all chunks, then final zero-length chunk and
1378 # then final CRLF.
1379 for i in range(0, chunkedSettings['chunksNumber']):
1380 if i > 0:
1381 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1382 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001383 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001384 self.sendChunkHelp('')
1385 return True
1386
creis@google.com2f4f6a42011-03-25 19:44:19 +00001387 def NoContentHandler(self):
1388 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001389
creis@google.com2f4f6a42011-03-25 19:44:19 +00001390 if not self._ShouldHandleRequest("/nocontent"):
1391 return False
1392 self.send_response(204)
1393 self.end_headers()
1394 return True
1395
initial.commit94958cf2008-07-26 22:42:52 +00001396 def ServerRedirectHandler(self):
1397 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001398 '/server-redirect?http://foo.bar/asdf' to redirect to
1399 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001400
1401 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001402 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001403 return False
1404
1405 query_char = self.path.find('?')
1406 if query_char < 0 or len(self.path) <= query_char + 1:
1407 self.sendRedirectHelp(test_name)
1408 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001409 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001410
1411 self.send_response(301) # moved permanently
1412 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001413 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001414 self.end_headers()
1415 self.wfile.write('<html><head>')
1416 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1417
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001418 return True
initial.commit94958cf2008-07-26 22:42:52 +00001419
naskoe7a0d0d2014-09-29 08:53:05 -07001420 def CrossSiteRedirectHandler(self):
1421 """Sends a server redirect to the given site. The syntax is
1422 '/cross-site/hostname/...' to redirect to //hostname/...
1423 It is used to navigate between different Sites, causing
1424 cross-site/cross-process navigations in the browser."""
1425
1426 test_name = "/cross-site"
1427 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001428 return False
1429
1430 params = urllib.unquote(self.path[(len(test_name) + 1):])
1431 slash = params.find('/')
1432 if slash < 0:
1433 self.sendRedirectHelp(test_name)
1434 return True
1435
1436 host = params[:slash]
1437 path = params[(slash+1):]
1438 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1439
1440 self.send_response(301) # moved permanently
1441 self.send_header('Location', dest)
1442 self.send_header('Content-Type', 'text/html')
1443 self.end_headers()
1444 self.wfile.write('<html><head>')
1445 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1446
1447 return True
1448
initial.commit94958cf2008-07-26 22:42:52 +00001449 def ClientRedirectHandler(self):
1450 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001451 '/client-redirect?http://foo.bar/asdf' to redirect to
1452 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001453
1454 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001455 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001456 return False
1457
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001458 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001459 if query_char < 0 or len(self.path) <= query_char + 1:
1460 self.sendRedirectHelp(test_name)
1461 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001462 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001463
1464 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001465 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001466 self.end_headers()
1467 self.wfile.write('<html><head>')
1468 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1469 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1470
1471 return True
1472
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001473 def GetSSLSessionCacheHandler(self):
1474 """Send a reply containing a log of the session cache operations."""
1475
1476 if not self._ShouldHandleRequest('/ssl-session-cache'):
1477 return False
1478
1479 self.send_response(200)
1480 self.send_header('Content-Type', 'text/plain')
1481 self.end_headers()
1482 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001483 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001484 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001485 self.wfile.write('Pass --https-record-resume in order to use' +
1486 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001487 return True
1488
1489 for (action, sessionID) in log:
1490 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001491 return True
1492
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001493 def SSLManySmallRecords(self):
1494 """Sends a reply consisting of a variety of small writes. These will be
1495 translated into a series of small SSL records when used over an HTTPS
1496 server."""
1497
1498 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1499 return False
1500
1501 self.send_response(200)
1502 self.send_header('Content-Type', 'text/plain')
1503 self.end_headers()
1504
1505 # Write ~26K of data, in 1350 byte chunks
1506 for i in xrange(20):
1507 self.wfile.write('*' * 1350)
1508 self.wfile.flush()
1509 return True
1510
agl@chromium.org04700be2013-03-02 18:40:41 +00001511 def GetChannelID(self):
1512 """Send a reply containing the hashed ChannelID that the client provided."""
1513
1514 if not self._ShouldHandleRequest('/channel-id'):
1515 return False
1516
1517 self.send_response(200)
1518 self.send_header('Content-Type', 'text/plain')
1519 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001520 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001521 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1522 return True
1523
nharper08eae822016-01-25 15:54:14 -08001524 def GetTokenBindingEKM(self):
1525 """Send a reply containing the EKM value for token binding from the TLS
1526 layer."""
1527
1528 if not self._ShouldHandleRequest('/tokbind-ekm'):
1529 return False
1530
1531 ekm = self.server.tlsConnection.exportKeyingMaterial(
1532 "EXPORTER-Token-Binding", "", False, 32)
1533 self.send_response(200)
1534 self.send_header('Content-Type', 'application/octet-stream')
1535 self.end_headers()
1536 self.wfile.write(ekm)
1537 return True
1538
nharpercb1adc32016-03-30 16:05:48 -07001539 def ForwardTokenBindingHeader(self):
nharpere758cd12016-07-13 17:49:36 -07001540 """Send a redirect that sets the Include-Referred-Token-Binding-ID
nharpercb1adc32016-03-30 16:05:48 -07001541 header."""
1542
1543 test_name = '/forward-tokbind'
1544 if not self._ShouldHandleRequest(test_name):
1545 return False
1546
1547 query_char = self.path.find('?')
1548 if query_char < 0 or len(self.path) <= query_char + 1:
1549 self.sendRedirectHelp(test_name)
1550 return True
1551 dest = urllib.unquote(self.path[query_char + 1:])
1552
1553 self.send_response(302)
1554 self.send_header('Location', dest)
nharpere758cd12016-07-13 17:49:36 -07001555 self.send_header('Include-Referred-Token-Binding-ID', 'true')
nharpercb1adc32016-03-30 16:05:48 -07001556 self.end_headers()
1557 return True
1558
pneubeckfd4f0442015-08-07 04:55:10 -07001559 def GetClientCert(self):
1560 """Send a reply whether a client certificate was provided."""
1561
1562 if not self._ShouldHandleRequest('/client-cert'):
1563 return False
1564
1565 self.send_response(200)
1566 self.send_header('Content-Type', 'text/plain')
1567 self.end_headers()
1568
1569 cert_chain = self.server.tlsConnection.session.clientCertChain
1570 if cert_chain != None:
1571 self.wfile.write('got client cert with fingerprint: ' +
1572 cert_chain.getFingerprint())
1573 else:
1574 self.wfile.write('got no client cert')
1575 return True
1576
davidben599e7e72014-09-03 16:19:09 -07001577 def ClientCipherListHandler(self):
1578 """Send a reply containing the cipher suite list that the client
1579 provided. Each cipher suite value is serialized in decimal, followed by a
1580 newline."""
1581
1582 if not self._ShouldHandleRequest('/client-cipher-list'):
1583 return False
1584
1585 self.send_response(200)
1586 self.send_header('Content-Type', 'text/plain')
1587 self.end_headers()
1588
davidben11682512014-10-06 21:09:11 -07001589 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1590 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001591 return True
1592
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001593 def CloseSocketHandler(self):
1594 """Closes the socket without sending anything."""
1595
1596 if not self._ShouldHandleRequest('/close-socket'):
1597 return False
1598
1599 self.wfile.close()
1600 return True
1601
initial.commit94958cf2008-07-26 22:42:52 +00001602 def DefaultResponseHandler(self):
1603 """This is the catch-all response handler for requests that aren't handled
1604 by one of the special handlers above.
1605 Note that we specify the content-length as without it the https connection
1606 is not closed properly (and the browser keeps expecting data)."""
1607
1608 contents = "Default response given for path: " + self.path
1609 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001610 self.send_header('Content-Type', 'text/html')
1611 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001612 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001613 if (self.command != 'HEAD'):
1614 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001615 return True
1616
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001617 def RedirectConnectHandler(self):
1618 """Sends a redirect to the CONNECT request for www.redirect.com. This
1619 response is not specified by the RFC, so the browser should not follow
1620 the redirect."""
1621
1622 if (self.path.find("www.redirect.com") < 0):
1623 return False
1624
1625 dest = "http://www.destination.com/foo.js"
1626
1627 self.send_response(302) # moved temporarily
1628 self.send_header('Location', dest)
1629 self.send_header('Connection', 'close')
1630 self.end_headers()
1631 return True
1632
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001633 def ServerAuthConnectHandler(self):
1634 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1635 response doesn't make sense because the proxy server cannot request
1636 server authentication."""
1637
1638 if (self.path.find("www.server-auth.com") < 0):
1639 return False
1640
1641 challenge = 'Basic realm="WallyWorld"'
1642
1643 self.send_response(401) # unauthorized
1644 self.send_header('WWW-Authenticate', challenge)
1645 self.send_header('Connection', 'close')
1646 self.end_headers()
1647 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001648
1649 def DefaultConnectResponseHandler(self):
1650 """This is the catch-all response handler for CONNECT requests that aren't
1651 handled by one of the special handlers above. Real Web servers respond
1652 with 400 to CONNECT requests."""
1653
1654 contents = "Your client has issued a malformed or illegal request."
1655 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001656 self.send_header('Content-Type', 'text/html')
1657 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001658 self.end_headers()
1659 self.wfile.write(contents)
1660 return True
1661
initial.commit94958cf2008-07-26 22:42:52 +00001662 # called by the redirect handling function when there is no parameter
1663 def sendRedirectHelp(self, redirect_name):
1664 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001665 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001666 self.end_headers()
1667 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1668 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1669 self.wfile.write('</body></html>')
1670
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001671 # called by chunked handling function
1672 def sendChunkHelp(self, chunk):
1673 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1674 self.wfile.write('%X\r\n' % len(chunk))
1675 self.wfile.write(chunk)
1676 self.wfile.write('\r\n')
1677
akalin@chromium.org154bb132010-11-12 02:20:27 +00001678
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001679class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001680 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001681 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001682 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001683 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001684 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001685 testserver_base.BasePageHandler.__init__(self, request, client_address,
1686 socket_server, [], handlers, [],
1687 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001688
1689 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001690 if self._ShouldHandleRequest("/ocsp"):
1691 response = self.ocsp_response
1692 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1693 response = self.ocsp_response_intermediate
1694 else:
mattm10ede842016-11-29 11:57:16 -08001695 return False
1696 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001697 self.send_response(200)
1698 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001699 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001700 self.end_headers()
1701
Matt Mueller55aef642018-05-02 18:53:57 +00001702 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001703
mattm10ede842016-11-29 11:57:16 -08001704 def CaIssuersResponse(self):
1705 if not self._ShouldHandleRequest("/ca_issuers"):
1706 return False
1707 print 'handling ca_issuers request'
1708 self.send_response(200)
1709 self.send_header('Content-Type', 'application/pkix-cert')
1710 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1711 self.end_headers()
1712
1713 self.wfile.write(self.ca_issuers_response)
1714
mattm@chromium.org830a3712012-11-07 23:00:07 +00001715
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001716class TCPEchoHandler(SocketServer.BaseRequestHandler):
1717 """The RequestHandler class for TCP echo server.
1718
1719 It is instantiated once per connection to the server, and overrides the
1720 handle() method to implement communication to the client.
1721 """
1722
1723 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001724 """Handles the request from the client and constructs a response."""
1725
1726 data = self.request.recv(65536).strip()
1727 # Verify the "echo request" message received from the client. Send back
1728 # "echo response" message if "echo request" message is valid.
1729 try:
1730 return_data = echo_message.GetEchoResponseData(data)
1731 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001732 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001733 except ValueError:
1734 return
1735
1736 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001737
1738
1739class UDPEchoHandler(SocketServer.BaseRequestHandler):
1740 """The RequestHandler class for UDP echo server.
1741
1742 It is instantiated once per connection to the server, and overrides the
1743 handle() method to implement communication to the client.
1744 """
1745
1746 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001747 """Handles the request from the client and constructs a response."""
1748
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001749 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001750 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001751 # Verify the "echo request" message received from the client. Send back
1752 # "echo response" message if "echo request" message is valid.
1753 try:
1754 return_data = echo_message.GetEchoResponseData(data)
1755 if not return_data:
1756 return
1757 except ValueError:
1758 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001759 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001760
1761
bashi@chromium.org33233532012-09-08 17:37:24 +00001762class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1763 """A request handler that behaves as a proxy server which requires
1764 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1765 """
1766
1767 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001768 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001769
1770 def parse_request(self):
1771 """Overrides parse_request to check credential."""
1772
1773 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1774 return False
1775
1776 auth = self.headers.getheader('Proxy-Authorization')
1777 if auth != self._AUTH_CREDENTIAL:
1778 self.send_response(407)
1779 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1780 self.end_headers()
1781 return False
1782
1783 return True
1784
1785 def _start_read_write(self, sock):
1786 sock.setblocking(0)
1787 self.request.setblocking(0)
1788 rlist = [self.request, sock]
1789 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001790 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001791 if errors:
1792 self.send_response(500)
1793 self.end_headers()
1794 return
1795 for s in ready_sockets:
1796 received = s.recv(1024)
1797 if len(received) == 0:
1798 return
1799 if s == self.request:
1800 other = sock
1801 else:
1802 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001803 # This will lose data if the kernel write buffer fills up.
1804 # TODO(ricea): Correctly use the return value to track how much was
1805 # written and buffer the rest. Use select to determine when the socket
1806 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001807 other.send(received)
1808
1809 def _do_common_method(self):
1810 url = urlparse.urlparse(self.path)
1811 port = url.port
1812 if not port:
1813 if url.scheme == 'http':
1814 port = 80
1815 elif url.scheme == 'https':
1816 port = 443
1817 if not url.hostname or not port:
1818 self.send_response(400)
1819 self.end_headers()
1820 return
1821
1822 if len(url.path) == 0:
1823 path = '/'
1824 else:
1825 path = url.path
1826 if len(url.query) > 0:
1827 path = '%s?%s' % (url.path, url.query)
1828
1829 sock = None
1830 try:
1831 sock = socket.create_connection((url.hostname, port))
1832 sock.send('%s %s %s\r\n' % (
1833 self.command, path, self.protocol_version))
1834 for header in self.headers.headers:
1835 header = header.strip()
1836 if (header.lower().startswith('connection') or
1837 header.lower().startswith('proxy')):
1838 continue
1839 sock.send('%s\r\n' % header)
1840 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001841 # This is wrong: it will pass through connection-level headers and
1842 # misbehave on connection reuse. The only reason it works at all is that
1843 # our test servers have never supported connection reuse.
1844 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001845 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001846 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001847 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001848 self.send_response(500)
1849 self.end_headers()
1850 finally:
1851 if sock is not None:
1852 sock.close()
1853
1854 def do_CONNECT(self):
1855 try:
1856 pos = self.path.rfind(':')
1857 host = self.path[:pos]
1858 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001859 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001860 self.send_response(400)
1861 self.end_headers()
1862
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001863 if BasicAuthProxyRequestHandler.redirect_connect_to_localhost:
1864 host = "127.0.0.1"
1865
Adam Rice54443aa2018-06-06 00:11:54 +00001866 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001867 try:
1868 sock = socket.create_connection((host, port))
1869 self.send_response(200, 'Connection established')
1870 self.end_headers()
1871 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001872 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001873 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001874 self.send_response(500)
1875 self.end_headers()
1876 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001877 if sock is not None:
1878 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001879
1880 def do_GET(self):
1881 self._do_common_method()
1882
1883 def do_HEAD(self):
1884 self._do_common_method()
1885
1886
mattm@chromium.org830a3712012-11-07 23:00:07 +00001887class ServerRunner(testserver_base.TestServerRunner):
1888 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001889
mattm@chromium.org830a3712012-11-07 23:00:07 +00001890 def __init__(self):
1891 super(ServerRunner, self).__init__()
1892 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001893
mattm@chromium.org830a3712012-11-07 23:00:07 +00001894 def __make_data_dir(self):
1895 if self.options.data_dir:
1896 if not os.path.isdir(self.options.data_dir):
1897 raise testserver_base.OptionError('specified data dir not found: ' +
1898 self.options.data_dir + ' exiting...')
1899 my_data_dir = self.options.data_dir
1900 else:
1901 # Create the default path to our data dir, relative to the exe dir.
1902 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1903 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001904
mattm@chromium.org830a3712012-11-07 23:00:07 +00001905 #TODO(ibrar): Must use Find* funtion defined in google\tools
1906 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001907
mattm@chromium.org830a3712012-11-07 23:00:07 +00001908 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001909
Matt Mueller55aef642018-05-02 18:53:57 +00001910 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1911 if states_option is None:
1912 return None, None, None
1913
1914 ocsp_states = list()
1915 for ocsp_state_arg in states_option.split(':'):
1916 if ocsp_state_arg == 'ok':
1917 ocsp_state = minica.OCSP_STATE_GOOD
1918 elif ocsp_state_arg == 'revoked':
1919 ocsp_state = minica.OCSP_STATE_REVOKED
1920 elif ocsp_state_arg == 'invalid':
1921 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1922 elif ocsp_state_arg == 'unauthorized':
1923 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1924 elif ocsp_state_arg == 'unknown':
1925 ocsp_state = minica.OCSP_STATE_UNKNOWN
1926 elif ocsp_state_arg == 'later':
1927 ocsp_state = minica.OCSP_STATE_TRY_LATER
1928 elif ocsp_state_arg == 'invalid_data':
1929 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1930 elif ocsp_state_arg == "mismatched_serial":
1931 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1932 else:
1933 raise testserver_base.OptionError('unknown OCSP status: ' +
1934 ocsp_state_arg)
1935 ocsp_states.append(ocsp_state)
1936
1937 if len(ocsp_states) > 1:
1938 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1939 raise testserver_base.OptionError('Multiple OCSP responses '
1940 'incompatible with states ' + str(ocsp_states))
1941
1942 ocsp_dates = list()
1943 for ocsp_date_arg in date_option.split(':'):
1944 if ocsp_date_arg == 'valid':
1945 ocsp_date = minica.OCSP_DATE_VALID
1946 elif ocsp_date_arg == 'old':
1947 ocsp_date = minica.OCSP_DATE_OLD
1948 elif ocsp_date_arg == 'early':
1949 ocsp_date = minica.OCSP_DATE_EARLY
1950 elif ocsp_date_arg == 'long':
1951 ocsp_date = minica.OCSP_DATE_LONG
1952 elif ocsp_date_arg == 'longer':
1953 ocsp_date = minica.OCSP_DATE_LONGER
1954 else:
1955 raise testserver_base.OptionError('unknown OCSP date: ' +
1956 ocsp_date_arg)
1957 ocsp_dates.append(ocsp_date)
1958
1959 if len(ocsp_states) != len(ocsp_dates):
1960 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1961 'count')
1962
1963 ocsp_produced = None
1964 if produced_option == 'valid':
1965 ocsp_produced = minica.OCSP_PRODUCED_VALID
1966 elif produced_option == 'before':
1967 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1968 elif produced_option == 'after':
1969 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1970 else:
1971 raise testserver_base.OptionError('unknown OCSP produced: ' +
1972 produced_option)
1973
1974 return ocsp_states, ocsp_dates, ocsp_produced
1975
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 def create_server(self, server_data):
1977 port = self.options.port
1978 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001979
Adam Rice54443aa2018-06-06 00:11:54 +00001980 logging.basicConfig()
1981
estark21667d62015-04-08 21:00:16 -07001982 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1983 # will result in a call to |getaddrinfo|, which fails with "nodename
1984 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001985 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001986 if self.options.server_type == SERVER_WEBSOCKET and \
1987 host == "localhost" and \
1988 port == 0:
1989 host = "127.0.0.1"
1990
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001991 # Construct the subjectAltNames for any ad-hoc generated certificates.
1992 # As host can be either a DNS name or IP address, attempt to determine
1993 # which it is, so it can be placed in the appropriate SAN.
1994 dns_sans = None
1995 ip_sans = None
1996 ip = None
1997 try:
1998 ip = socket.inet_aton(host)
1999 ip_sans = [ip]
2000 except socket.error:
2001 pass
2002 if ip is None:
2003 dns_sans = [host]
2004
mattm@chromium.org830a3712012-11-07 23:00:07 +00002005 if self.options.server_type == SERVER_HTTP:
2006 if self.options.https:
2007 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08002008 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 if self.options.cert_and_key_file:
2010 if not os.path.isfile(self.options.cert_and_key_file):
2011 raise testserver_base.OptionError(
2012 'specified server cert file not found: ' +
2013 self.options.cert_and_key_file + ' exiting...')
2014 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08002015 elif self.options.aia_intermediate:
2016 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2017 print ('AIA server started on %s:%d...' %
2018 (host, self.__ocsp_server.server_port))
2019
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002020 ocsp_server_port = self.__ocsp_server.server_port
2021 if self.options.ocsp_proxy_port_number != 0:
2022 ocsp_server_port = self.options.ocsp_proxy_port_number
2023 server_data['ocsp_port'] = self.__ocsp_server.server_port
2024
mattm10ede842016-11-29 11:57:16 -08002025 (pem_cert_and_key, intermediate_cert_der) = \
2026 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00002027 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002028 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002029 ca_issuers_url =
2030 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08002031 serial = self.options.cert_serial)
2032
2033 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00002034 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08002035 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002036 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002037 # generate a new certificate and run an OCSP server for it.
2038 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002039 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002041
Matt Mueller55aef642018-05-02 18:53:57 +00002042 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
2043 self.options.ocsp,
2044 self.options.ocsp_date,
2045 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002046
Matt Mueller55aef642018-05-02 18:53:57 +00002047 (ocsp_intermediate_states, ocsp_intermediate_dates,
2048 ocsp_intermediate_produced) = self.__parse_ocsp_options(
2049 self.options.ocsp_intermediate,
2050 self.options.ocsp_intermediate_date,
2051 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002052
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002053 ocsp_server_port = self.__ocsp_server.server_port
2054 if self.options.ocsp_proxy_port_number != 0:
2055 ocsp_server_port = self.options.ocsp_proxy_port_number
2056 server_data['ocsp_port'] = self.__ocsp_server.server_port
2057
Matt Mueller55aef642018-05-02 18:53:57 +00002058 pem_cert_and_key, (ocsp_der,
2059 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00002060 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002061 ip_sans = ip_sans,
2062 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002063 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07002064 ocsp_states = ocsp_states,
2065 ocsp_dates = ocsp_dates,
2066 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00002067 ocsp_intermediate_url = (
2068 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
2069 if ocsp_intermediate_states else None),
2070 ocsp_intermediate_states = ocsp_intermediate_states,
2071 ocsp_intermediate_dates = ocsp_intermediate_dates,
2072 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002073 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002074
davidben3e2564a2014-11-07 18:51:00 -08002075 if self.options.ocsp_server_unavailable:
2076 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00002077 self.__ocsp_server.ocsp_response_intermediate = \
2078 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08002079 else:
2080 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00002081 self.__ocsp_server.ocsp_response_intermediate = \
2082 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08002083 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002084
2085 for ca_cert in self.options.ssl_client_ca:
2086 if not os.path.isfile(ca_cert):
2087 raise testserver_base.OptionError(
2088 'specified trusted client CA file not found: ' + ca_cert +
2089 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002090
2091 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002092 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00002093 # TODO(mattm): Staple the intermediate response too (if applicable,
2094 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08002095 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002096
mattm@chromium.org830a3712012-11-07 23:00:07 +00002097 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2098 self.options.ssl_client_auth,
2099 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002100 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002101 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002102 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002103 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002104 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002105 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002106 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002107 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002108 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002109 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002110 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002111 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002112 self.options.alert_after_handshake,
2113 self.options.disable_channel_id,
2114 self.options.disable_extended_master_secret,
2115 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002116 print 'HTTPS server started on https://%s:%d...' % \
2117 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002118 else:
2119 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002120 print 'HTTP server started on http://%s:%d...' % \
2121 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002122
2123 server.data_dir = self.__make_data_dir()
2124 server.file_root_url = self.options.file_root_url
2125 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002126 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002127 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2128 # is required to work correctly. It should be fixed from pywebsocket side.
2129 os.chdir(self.__make_data_dir())
2130 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002131 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002132 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002133 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002135 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2136 if not os.path.isfile(key_path):
2137 raise testserver_base.OptionError(
2138 'specified server cert file not found: ' +
2139 self.options.cert_and_key_file + ' exiting...')
2140 websocket_options.private_key = key_path
2141 websocket_options.certificate = key_path
2142
mattm@chromium.org830a3712012-11-07 23:00:07 +00002143 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002144 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002145 websocket_options.tls_client_auth = True
2146 if len(self.options.ssl_client_ca) != 1:
2147 raise testserver_base.OptionError(
2148 'one trusted client CA file should be specified')
2149 if not os.path.isfile(self.options.ssl_client_ca[0]):
2150 raise testserver_base.OptionError(
2151 'specified trusted client CA file not found: ' +
2152 self.options.ssl_client_ca[0] + ' exiting...')
2153 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002154 print 'Trying to start websocket server on %s://%s:%d...' % \
2155 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002156 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002157 print 'WebSocket server started on %s://%s:%d...' % \
2158 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002159 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002160 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002161 elif self.options.server_type == SERVER_TCP_ECHO:
2162 # Used for generating the key (randomly) that encodes the "echo request"
2163 # message.
2164 random.seed()
2165 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002166 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002167 server_data['port'] = server.server_port
2168 elif self.options.server_type == SERVER_UDP_ECHO:
2169 # Used for generating the key (randomly) that encodes the "echo request"
2170 # message.
2171 random.seed()
2172 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002173 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002174 server_data['port'] = server.server_port
2175 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002176 BasicAuthProxyRequestHandler.redirect_connect_to_localhost = \
2177 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00002178 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002179 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002180 server_data['port'] = server.server_port
2181 elif self.options.server_type == SERVER_FTP:
2182 my_data_dir = self.__make_data_dir()
2183
2184 # Instantiate a dummy authorizer for managing 'virtual' users
2185 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2186
xleng9d4c45f2015-05-04 16:26:12 -07002187 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002188 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2189
xleng9d4c45f2015-05-04 16:26:12 -07002190 # Define a read-only anonymous user unless disabled
2191 if not self.options.no_anonymous_ftp_user:
2192 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002193
2194 # Instantiate FTP handler class
2195 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2196 ftp_handler.authorizer = authorizer
2197
2198 # Define a customized banner (string returned when client connects)
2199 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2200 pyftpdlib.ftpserver.__ver__)
2201
2202 # Instantiate FTP server class and listen to address:port
2203 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2204 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002205 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002206 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002207 raise testserver_base.OptionError('unknown server type' +
2208 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002209
mattm@chromium.org830a3712012-11-07 23:00:07 +00002210 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002211
mattm@chromium.org830a3712012-11-07 23:00:07 +00002212 def run_server(self):
2213 if self.__ocsp_server:
2214 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002215
mattm@chromium.org830a3712012-11-07 23:00:07 +00002216 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002217
mattm@chromium.org830a3712012-11-07 23:00:07 +00002218 if self.__ocsp_server:
2219 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002220
mattm@chromium.org830a3712012-11-07 23:00:07 +00002221 def add_options(self):
2222 testserver_base.TestServerRunner.add_options(self)
2223 self.option_parser.add_option('-f', '--ftp', action='store_const',
2224 const=SERVER_FTP, default=SERVER_HTTP,
2225 dest='server_type',
2226 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002227 self.option_parser.add_option('--tcp-echo', action='store_const',
2228 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2229 dest='server_type',
2230 help='start up a tcp echo server.')
2231 self.option_parser.add_option('--udp-echo', action='store_const',
2232 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2233 dest='server_type',
2234 help='start up a udp echo server.')
2235 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2236 const=SERVER_BASIC_AUTH_PROXY,
2237 default=SERVER_HTTP, dest='server_type',
2238 help='start up a proxy server which requires '
2239 'basic authentication.')
2240 self.option_parser.add_option('--websocket', action='store_const',
2241 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2242 dest='server_type',
2243 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002244 self.option_parser.add_option('--https', action='store_true',
2245 dest='https', help='Specify that https '
2246 'should be used.')
2247 self.option_parser.add_option('--cert-and-key-file',
2248 dest='cert_and_key_file', help='specify the '
2249 'path to the file containing the certificate '
2250 'and private key for the server in PEM '
2251 'format')
mattm10ede842016-11-29 11:57:16 -08002252 self.option_parser.add_option('--aia-intermediate', action='store_true',
2253 dest='aia_intermediate',
2254 help='generate a certificate chain that '
2255 'requires AIA cert fetching, and run a '
2256 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002257 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2258 help='The type of OCSP response generated '
2259 'for the automatically generated '
2260 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002261 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2262 default='valid', help='The validity of the '
2263 'range between thisUpdate and nextUpdate')
2264 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2265 default='valid', help='producedAt relative '
2266 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002267 self.option_parser.add_option('--ocsp-intermediate',
2268 dest='ocsp_intermediate', default=None,
2269 help='If specified, the automatically '
2270 'generated chain will include an '
2271 'intermediate certificate with this type '
2272 'of OCSP response (see docs for --ocsp)')
2273 self.option_parser.add_option('--ocsp-intermediate-date',
2274 dest='ocsp_intermediate_date',
2275 default='valid', help='The validity of the '
2276 'range between thisUpdate and nextUpdate')
2277 self.option_parser.add_option('--ocsp-intermediate-produced',
2278 dest='ocsp_intermediate_produced',
2279 default='valid', help='producedAt relative '
2280 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002281 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2282 default=0, type=int,
2283 help='If non-zero then the generated '
2284 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002285 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2286 default="127.0.0.1",
2287 help='The generated certificate will have '
2288 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002289 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2290 default='0', type='int',
2291 help='If nonzero, certain TLS connections '
2292 'will be aborted in order to test version '
2293 'fallback. 1 means all TLS versions will be '
2294 'aborted. 2 means TLS 1.1 or higher will be '
2295 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002296 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002297 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002298 self.option_parser.add_option('--tls-intolerance-type',
2299 dest='tls_intolerance_type',
2300 default="alert",
2301 help='Controls how the server reacts to a '
2302 'TLS version it is intolerant to. Valid '
2303 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002304 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2305 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002306 default='',
2307 help='Base64 encoded SCT list. If set, '
2308 'server will respond with a '
2309 'signed_certificate_timestamp TLS extension '
2310 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002311 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2312 default=False, const=True,
2313 action='store_const',
2314 help='If given, TLS_FALLBACK_SCSV support '
2315 'will be enabled. This causes the server to '
2316 'reject fallback connections from compatible '
2317 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002318 self.option_parser.add_option('--staple-ocsp-response',
2319 dest='staple_ocsp_response',
2320 default=False, action='store_true',
2321 help='If set, server will staple the OCSP '
2322 'response whenever OCSP is on and the client '
2323 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002324 self.option_parser.add_option('--https-record-resume',
2325 dest='record_resume', const=True,
2326 default=False, action='store_const',
2327 help='Record resumption cache events rather '
2328 'than resuming as normal. Allows the use of '
2329 'the /ssl-session-cache request')
2330 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2331 help='Require SSL client auth on every '
2332 'connection.')
2333 self.option_parser.add_option('--ssl-client-ca', action='append',
2334 default=[], help='Specify that the client '
2335 'certificate request should include the CA '
2336 'named in the subject of the DER-encoded '
2337 'certificate contained in the specified '
2338 'file. This option may appear multiple '
2339 'times, indicating multiple CA names should '
2340 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002341 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2342 default=[], help='Specify that the client '
2343 'certificate request should include the '
2344 'specified certificate_type value. This '
2345 'option may appear multiple times, '
2346 'indicating multiple values should be send '
2347 'in the request. Valid values are '
2348 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2349 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002350 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2351 help='Specify the bulk encryption '
2352 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002353 'SSL server. Valid values are "aes128gcm", '
2354 '"aes256", "aes128", "3des", "rc4". If '
2355 'omitted, all algorithms will be used. This '
2356 'option may appear multiple times, '
2357 'indicating multiple algorithms should be '
2358 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002359 self.option_parser.add_option('--ssl-key-exchange', action='append',
2360 help='Specify the key exchange algorithm(s)'
2361 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002362 'Valid values are "rsa", "dhe_rsa", '
2363 '"ecdhe_rsa". If omitted, all algorithms '
2364 'will be used. This option may appear '
2365 'multiple times, indicating multiple '
2366 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002367 self.option_parser.add_option('--alpn-protocols', action='append',
2368 help='Specify the list of ALPN protocols. '
2369 'The server will not send an ALPN response '
2370 'if this list does not overlap with the '
2371 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002372 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002373 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002374 'an NPN response. The server will not'
2375 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002376 self.option_parser.add_option('--file-root-url', default='/files/',
2377 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002378 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2379 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2380 dest='ws_basic_auth',
2381 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002382 self.option_parser.add_option('--ocsp-server-unavailable',
2383 dest='ocsp_server_unavailable',
2384 default=False, action='store_true',
2385 help='If set, the OCSP server will return '
2386 'a tryLater status rather than the actual '
2387 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002388 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2389 type='int', dest='ocsp_proxy_port_number',
2390 help='Port allocated for OCSP proxy '
2391 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002392 self.option_parser.add_option('--alert-after-handshake',
2393 dest='alert_after_handshake',
2394 default=False, action='store_true',
2395 help='If set, the server will send a fatal '
2396 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002397 self.option_parser.add_option('--no-anonymous-ftp-user',
2398 dest='no_anonymous_ftp_user',
2399 default=False, action='store_true',
2400 help='If set, the FTP server will not create '
2401 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002402 self.option_parser.add_option('--disable-channel-id', action='store_true')
2403 self.option_parser.add_option('--disable-extended-master-secret',
2404 action='store_true')
2405 self.option_parser.add_option('--token-binding-params', action='append',
2406 default=[], type='int')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002407 self.option_parser.add_option('--redirect-connect-to-localhost',
2408 dest='redirect_connect_to_localhost',
2409 default=False, action='store_true',
2410 help='If set, the Proxy server will connect '
2411 'to localhost instead of the requested URL '
2412 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002413
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002414
initial.commit94958cf2008-07-26 22:42:52 +00002415if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002416 sys.exit(ServerRunner().main())