blob: cf55990a24fd0090cf78445272f3c542b306905d [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
Adam Rice9476b8c2018-08-02 15:28:43 +00006"""This is a simple HTTP/FTP/TCP/UDP/PROXY/BASIC_AUTH_PROXY/WEBSOCKET server
7used for testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Insert at the beginning of the path, we want to use our copies of the library
Robert Iannucci0e7ec952018-01-18 22:44:16 +000043# unconditionally (since they contain modifications from anything that might be
44# obtained from e.g. PyPi).
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000046sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
47
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000048import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000049from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000050# import manually
51mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000052
davidben@chromium.org7d53b542014-04-10 17:56:44 +000053import pyftpdlib.ftpserver
54
55import tlslite
56import tlslite.api
57
58import echo_message
59import testserver_base
60
maruel@chromium.org756cf982009-03-05 12:46:38 +000061SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000062SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000063SERVER_TCP_ECHO = 2
64SERVER_UDP_ECHO = 3
65SERVER_BASIC_AUTH_PROXY = 4
66SERVER_WEBSOCKET = 5
Adam Rice9476b8c2018-08-02 15:28:43 +000067SERVER_PROXY = 6
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000068
69# Default request queue size for WebSocketServer.
70_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000071
dadrian4ccf51c2016-07-20 15:36:58 -070072OCSP_STATES_NO_SINGLE_RESPONSE = {
73 minica.OCSP_STATE_INVALID_RESPONSE,
74 minica.OCSP_STATE_UNAUTHORIZED,
75 minica.OCSP_STATE_TRY_LATER,
76 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
77}
78
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079class WebSocketOptions:
80 """Holds options for WebSocketServer."""
81
82 def __init__(self, host, port, data_dir):
83 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
84 self.server_host = host
85 self.port = port
86 self.websock_handlers = data_dir
87 self.scan_dir = None
88 self.allow_handlers_outside_root_dir = False
89 self.websock_handlers_map_file = None
90 self.cgi_directories = []
91 self.is_executable_method = None
92 self.allow_draft75 = False
93 self.strict = True
94
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000095 self.use_tls = False
96 self.private_key = None
97 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +000098 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000099 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +0000100 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000101 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000102 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000103
mattm@chromium.org830a3712012-11-07 23:00:07 +0000104
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000105class RecordingSSLSessionCache(object):
106 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
107 lookups and inserts in order to test session cache behaviours."""
108
109 def __init__(self):
110 self.log = []
111
112 def __getitem__(self, sessionID):
113 self.log.append(('lookup', sessionID))
114 raise KeyError()
115
116 def __setitem__(self, sessionID, session):
117 self.log.append(('insert', sessionID))
118
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000119
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000120class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
121 testserver_base.BrokenPipeHandlerMixIn,
122 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000123 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000124 verification."""
125
126 pass
127
Adam Rice34b2e312018-04-06 16:48:30 +0000128class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
129 HTTPServer):
130 """This variant of HTTPServer creates a new thread for every connection. It
131 should only be used with handlers that are known to be threadsafe."""
132
133 pass
134
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000135class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
136 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000137 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000138 """This is a specialization of HTTPServer that serves an
139 OCSP response"""
140
141 def serve_forever_on_thread(self):
142 self.thread = threading.Thread(target = self.serve_forever,
143 name = "OCSPServerThread")
144 self.thread.start()
145
146 def stop_serving(self):
147 self.shutdown()
148 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000149
mattm@chromium.org830a3712012-11-07 23:00:07 +0000150
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000151class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000152 testserver_base.ClientRestrictingServerMixIn,
153 testserver_base.BrokenPipeHandlerMixIn,
154 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000155 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000156 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000157
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000158 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000159 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700160 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
161 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000162 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700163 fallback_scsv_enabled, ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -0700164 alert_after_handshake, disable_channel_id, disable_ems,
165 token_binding_params):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000166 self.cert_chain = tlslite.api.X509CertChain()
167 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000168 # Force using only python implementation - otherwise behavior is different
169 # depending on whether m2crypto Python module is present (error is thrown
170 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
171 # the hood.
172 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
173 private=True,
174 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000175 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000176 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000177 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700178 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000179 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000180 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000181 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000182
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000183 if ssl_client_auth:
184 for ca_file in ssl_client_cas:
185 s = open(ca_file).read()
186 x509 = tlslite.api.X509()
187 x509.parse(s)
188 self.ssl_client_cas.append(x509.subject)
189
190 for cert_type in ssl_client_cert_types:
191 self.ssl_client_cert_types.append({
192 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000193 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
194 }[cert_type])
195
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000196 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800197 # Enable SSLv3 for testing purposes.
198 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000199 if ssl_bulk_ciphers is not None:
200 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000201 if ssl_key_exchanges is not None:
202 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000203 if tls_intolerant != 0:
204 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
205 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700206 if alert_after_handshake:
207 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700208 if disable_channel_id:
209 self.ssl_handshake_settings.enableChannelID = False
210 if disable_ems:
211 self.ssl_handshake_settings.enableExtendedMasterSecret = False
212 self.ssl_handshake_settings.supportedTokenBindingParams = \
213 token_binding_params
bnc5fb33bd2016-08-05 12:09:21 -0700214 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000215
rsleevi8146efa2015-03-16 12:31:24 -0700216 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000217 # If record_resume_info is true then we'll replace the session cache with
218 # an object that records the lookups and inserts that it sees.
219 self.session_cache = RecordingSSLSessionCache()
220 else:
221 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000222 testserver_base.StoppableHTTPServer.__init__(self,
223 server_address,
224 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000225
226 def handshake(self, tlsConnection):
227 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000228
initial.commit94958cf2008-07-26 22:42:52 +0000229 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000230 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000231 tlsConnection.handshakeServer(certChain=self.cert_chain,
232 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000233 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000234 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000235 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000236 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000237 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700238 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000239 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000240 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000241 fallbackSCSV=self.fallback_scsv_enabled,
242 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000243 tlsConnection.ignoreAbruptClose = True
244 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000245 except tlslite.api.TLSAbruptCloseError:
246 # Ignore abrupt close.
247 return True
initial.commit94958cf2008-07-26 22:42:52 +0000248 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000249 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000250 return False
251
akalin@chromium.org154bb132010-11-12 02:20:27 +0000252
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000253class FTPServer(testserver_base.ClientRestrictingServerMixIn,
254 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000255 """This is a specialization of FTPServer that adds client verification."""
256
257 pass
258
259
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000260class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
261 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000262 """A TCP echo server that echoes back what it has received."""
263
264 def server_bind(self):
265 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000266
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000267 SocketServer.TCPServer.server_bind(self)
268 host, port = self.socket.getsockname()[:2]
269 self.server_name = socket.getfqdn(host)
270 self.server_port = port
271
272 def serve_forever(self):
273 self.stop = False
274 self.nonce_time = None
275 while not self.stop:
276 self.handle_request()
277 self.socket.close()
278
279
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000280class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
281 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000282 """A UDP echo server that echoes back what it has received."""
283
284 def server_bind(self):
285 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000286
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000287 SocketServer.UDPServer.server_bind(self)
288 host, port = self.socket.getsockname()[:2]
289 self.server_name = socket.getfqdn(host)
290 self.server_port = port
291
292 def serve_forever(self):
293 self.stop = False
294 self.nonce_time = None
295 while not self.stop:
296 self.handle_request()
297 self.socket.close()
298
299
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000300class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000301 # Class variables to allow for persistence state between page handler
302 # invocations
303 rst_limits = {}
304 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000305
306 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000307 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000308 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000309 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000310 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000311 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000312 self.NoCacheMaxAgeTimeHandler,
313 self.NoCacheTimeHandler,
314 self.CacheTimeHandler,
315 self.CacheExpiresHandler,
316 self.CacheProxyRevalidateHandler,
317 self.CachePrivateHandler,
318 self.CachePublicHandler,
319 self.CacheSMaxAgeHandler,
320 self.CacheMustRevalidateHandler,
321 self.CacheMustRevalidateMaxAgeHandler,
322 self.CacheNoStoreHandler,
323 self.CacheNoStoreMaxAgeHandler,
324 self.CacheNoTransformHandler,
325 self.DownloadHandler,
326 self.DownloadFinishHandler,
327 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000328 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000329 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000330 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000331 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000332 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000333 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000334 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000335 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000336 self.AuthBasicHandler,
337 self.AuthDigestHandler,
338 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000339 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000340 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000341 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700342 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000343 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000344 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000345 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000346 self.GetChannelID,
nharper08eae822016-01-25 15:54:14 -0800347 self.GetTokenBindingEKM,
nharpercb1adc32016-03-30 16:05:48 -0700348 self.ForwardTokenBindingHeader,
pneubeckfd4f0442015-08-07 04:55:10 -0700349 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700350 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000351 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000352 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000353 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000354 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000355 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000356 self.PostOnlyFileHandler,
357 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000358 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000359 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000360 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000361 head_handlers = [
362 self.FileHandler,
363 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000364
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000366 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000367 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000368 'gif': 'image/gif',
369 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000370 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700371 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000372 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000373 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000374 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000375 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000376 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000377 }
initial.commit94958cf2008-07-26 22:42:52 +0000378 self._default_mime_type = 'text/html'
379
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000380 testserver_base.BasePageHandler.__init__(self, request, client_address,
381 socket_server, connect_handlers,
382 get_handlers, head_handlers,
383 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000384
initial.commit94958cf2008-07-26 22:42:52 +0000385 def GetMIMETypeFromName(self, file_name):
386 """Returns the mime type for the specified file_name. So far it only looks
387 at the file extension."""
388
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000389 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000390 if len(extension) == 0:
391 # no extension.
392 return self._default_mime_type
393
ericroman@google.comc17ca532009-05-07 03:51:05 +0000394 # extension starts with a dot, so we need to remove it
395 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000396
initial.commit94958cf2008-07-26 22:42:52 +0000397 def NoCacheMaxAgeTimeHandler(self):
398 """This request handler yields a page with the title set to the current
399 system time, and no caching requested."""
400
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000401 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000402 return False
403
404 self.send_response(200)
405 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000406 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000407 self.end_headers()
408
maruel@google.come250a9b2009-03-10 17:39:46 +0000409 self.wfile.write('<html><head><title>%s</title></head></html>' %
410 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000411
412 return True
413
414 def NoCacheTimeHandler(self):
415 """This request handler yields a page with the title set to the current
416 system time, and no caching requested."""
417
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000418 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000419 return False
420
421 self.send_response(200)
422 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000423 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000424 self.end_headers()
425
maruel@google.come250a9b2009-03-10 17:39:46 +0000426 self.wfile.write('<html><head><title>%s</title></head></html>' %
427 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000428
429 return True
430
431 def CacheTimeHandler(self):
432 """This request handler yields a page with the title set to the current
433 system time, and allows caching for one minute."""
434
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 self.send_response(200)
439 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000440 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000441 self.end_headers()
442
maruel@google.come250a9b2009-03-10 17:39:46 +0000443 self.wfile.write('<html><head><title>%s</title></head></html>' %
444 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000445
446 return True
447
448 def CacheExpiresHandler(self):
449 """This request handler yields a page with the title set to the current
450 system time, and set the page to expire on 1 Jan 2099."""
451
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000452 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000453 return False
454
455 self.send_response(200)
456 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000457 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000458 self.end_headers()
459
maruel@google.come250a9b2009-03-10 17:39:46 +0000460 self.wfile.write('<html><head><title>%s</title></head></html>' %
461 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000462
463 return True
464
465 def CacheProxyRevalidateHandler(self):
466 """This request handler yields a page with the title set to the current
467 system time, and allows caching for 60 seconds"""
468
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000469 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000470 return False
471
472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000473 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000474 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
475 self.end_headers()
476
maruel@google.come250a9b2009-03-10 17:39:46 +0000477 self.wfile.write('<html><head><title>%s</title></head></html>' %
478 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000479
480 return True
481
482 def CachePrivateHandler(self):
483 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700484 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000485
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000486 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000487 return False
488
489 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000490 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000491 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000492 self.end_headers()
493
maruel@google.come250a9b2009-03-10 17:39:46 +0000494 self.wfile.write('<html><head><title>%s</title></head></html>' %
495 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000496
497 return True
498
499 def CachePublicHandler(self):
500 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700501 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000502
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000503 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000504 return False
505
506 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000507 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000508 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000509 self.end_headers()
510
maruel@google.come250a9b2009-03-10 17:39:46 +0000511 self.wfile.write('<html><head><title>%s</title></head></html>' %
512 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000513
514 return True
515
516 def CacheSMaxAgeHandler(self):
517 """This request handler yields a page with the title set to the current
518 system time, and does not allow for caching."""
519
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000520 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000521 return False
522
523 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000524 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000525 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
526 self.end_headers()
527
maruel@google.come250a9b2009-03-10 17:39:46 +0000528 self.wfile.write('<html><head><title>%s</title></head></html>' %
529 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000530
531 return True
532
533 def CacheMustRevalidateHandler(self):
534 """This request handler yields a page with the title set to the current
535 system time, and does not allow caching."""
536
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000537 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000538 return False
539
540 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000541 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000542 self.send_header('Cache-Control', 'must-revalidate')
543 self.end_headers()
544
maruel@google.come250a9b2009-03-10 17:39:46 +0000545 self.wfile.write('<html><head><title>%s</title></head></html>' %
546 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000547
548 return True
549
550 def CacheMustRevalidateMaxAgeHandler(self):
551 """This request handler yields a page with the title set to the current
552 system time, and does not allow caching event though max-age of 60
553 seconds is specified."""
554
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000555 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000556 return False
557
558 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000559 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000560 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
561 self.end_headers()
562
maruel@google.come250a9b2009-03-10 17:39:46 +0000563 self.wfile.write('<html><head><title>%s</title></head></html>' %
564 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000565
566 return True
567
initial.commit94958cf2008-07-26 22:42:52 +0000568 def CacheNoStoreHandler(self):
569 """This request handler yields a page with the title set to the current
570 system time, and does not allow the page to be stored."""
571
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000572 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000573 return False
574
575 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000576 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000577 self.send_header('Cache-Control', 'no-store')
578 self.end_headers()
579
maruel@google.come250a9b2009-03-10 17:39:46 +0000580 self.wfile.write('<html><head><title>%s</title></head></html>' %
581 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000582
583 return True
584
585 def CacheNoStoreMaxAgeHandler(self):
586 """This request handler yields a page with the title set to the current
587 system time, and does not allow the page to be stored even though max-age
588 of 60 seconds is specified."""
589
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000590 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000591 return False
592
593 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000594 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000595 self.send_header('Cache-Control', 'max-age=60, no-store')
596 self.end_headers()
597
maruel@google.come250a9b2009-03-10 17:39:46 +0000598 self.wfile.write('<html><head><title>%s</title></head></html>' %
599 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000600
601 return True
602
603
604 def CacheNoTransformHandler(self):
605 """This request handler yields a page with the title set to the current
606 system time, and does not allow the content to transformed during
607 user-agent caching"""
608
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000609 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000610 return False
611
612 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000613 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000614 self.send_header('Cache-Control', 'no-transform')
615 self.end_headers()
616
maruel@google.come250a9b2009-03-10 17:39:46 +0000617 self.wfile.write('<html><head><title>%s</title></head></html>' %
618 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000619
620 return True
621
622 def EchoHeader(self):
623 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000624
ananta@chromium.org219b2062009-10-23 16:09:41 +0000625 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000626
ananta@chromium.org56812d02011-04-07 17:52:05 +0000627 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000628 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000629 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000630
ananta@chromium.org56812d02011-04-07 17:52:05 +0000631 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000632
633 def EchoHeaderHelper(self, echo_header):
634 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000635
ananta@chromium.org219b2062009-10-23 16:09:41 +0000636 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000637 return False
638
639 query_char = self.path.find('?')
640 if query_char != -1:
641 header_name = self.path[query_char+1:]
642
643 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000644 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000645 if echo_header == '/echoheadercache':
646 self.send_header('Cache-control', 'max-age=60000')
647 else:
648 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000649 # insert a vary header to properly indicate that the cachability of this
650 # request is subject to value of the request header being echoed.
651 if len(header_name) > 0:
652 self.send_header('Vary', header_name)
653 self.end_headers()
654
655 if len(header_name) > 0:
656 self.wfile.write(self.headers.getheader(header_name))
657
658 return True
659
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000660 def ReadRequestBody(self):
661 """This function reads the body of the current HTTP request, handling
662 both plain and chunked transfer encoded requests."""
663
664 if self.headers.getheader('transfer-encoding') != 'chunked':
665 length = int(self.headers.getheader('content-length'))
666 return self.rfile.read(length)
667
668 # Read the request body as chunks.
669 body = ""
670 while True:
671 line = self.rfile.readline()
672 length = int(line, 16)
673 if length == 0:
674 self.rfile.readline()
675 break
676 body += self.rfile.read(length)
677 self.rfile.read(2)
678 return body
679
initial.commit94958cf2008-07-26 22:42:52 +0000680 def EchoHandler(self):
681 """This handler just echoes back the payload of the request, for testing
682 form submission."""
683
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000684 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000685 return False
686
hirono2838c572015-01-21 12:18:11 -0800687 _, _, _, _, query, _ = urlparse.urlparse(self.path)
688 query_params = cgi.parse_qs(query, True)
689 if 'status' in query_params:
690 self.send_response(int(query_params['status'][0]))
691 else:
692 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000693 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000694 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000695 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000696 return True
697
698 def EchoTitleHandler(self):
699 """This handler is like Echo, but sets the page title to the request."""
700
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000701 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000702 return False
703
704 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000705 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000706 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000707 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000708 self.wfile.write('<html><head><title>')
709 self.wfile.write(request)
710 self.wfile.write('</title></head></html>')
711 return True
712
713 def EchoAllHandler(self):
714 """This handler yields a (more) human-readable page listing information
715 about the request header & contents."""
716
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000717 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000718 return False
719
720 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000721 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000722 self.end_headers()
723 self.wfile.write('<html><head><style>'
724 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
725 '</style></head><body>'
726 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000727 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000728 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000729
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000730 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000731 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000732 params = cgi.parse_qs(qs, keep_blank_values=1)
733
734 for param in params:
735 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000736
737 self.wfile.write('</pre>')
738
739 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
740
741 self.wfile.write('</body></html>')
742 return True
743
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000744 def EchoMultipartPostHandler(self):
745 """This handler echoes received multipart post data as json format."""
746
747 if not (self._ShouldHandleRequest("/echomultipartpost") or
748 self._ShouldHandleRequest("/searchbyimage")):
749 return False
750
751 content_type, parameters = cgi.parse_header(
752 self.headers.getheader('content-type'))
753 if content_type == 'multipart/form-data':
754 post_multipart = cgi.parse_multipart(self.rfile, parameters)
755 elif content_type == 'application/x-www-form-urlencoded':
756 raise Exception('POST by application/x-www-form-urlencoded is '
757 'not implemented.')
758 else:
759 post_multipart = {}
760
761 # Since the data can be binary, we encode them by base64.
762 post_multipart_base64_encoded = {}
763 for field, values in post_multipart.items():
764 post_multipart_base64_encoded[field] = [base64.b64encode(value)
765 for value in values]
766
767 result = {'POST_multipart' : post_multipart_base64_encoded}
768
769 self.send_response(200)
770 self.send_header("Content-type", "text/plain")
771 self.end_headers()
772 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
773 return True
774
initial.commit94958cf2008-07-26 22:42:52 +0000775 def DownloadHandler(self):
776 """This handler sends a downloadable file with or without reporting
777 the size (6K)."""
778
779 if self.path.startswith("/download-unknown-size"):
780 send_length = False
781 elif self.path.startswith("/download-known-size"):
782 send_length = True
783 else:
784 return False
785
786 #
787 # The test which uses this functionality is attempting to send
788 # small chunks of data to the client. Use a fairly large buffer
789 # so that we'll fill chrome's IO buffer enough to force it to
790 # actually write the data.
791 # See also the comments in the client-side of this test in
792 # download_uitest.cc
793 #
794 size_chunk1 = 35*1024
795 size_chunk2 = 10*1024
796
797 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000798 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000799 self.send_header('Cache-Control', 'max-age=0')
800 if send_length:
801 self.send_header('Content-Length', size_chunk1 + size_chunk2)
802 self.end_headers()
803
804 # First chunk of data:
805 self.wfile.write("*" * size_chunk1)
806 self.wfile.flush()
807
808 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000809 self.server.wait_for_download = True
810 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000811 self.server.handle_request()
812
813 # Second chunk of data:
814 self.wfile.write("*" * size_chunk2)
815 return True
816
817 def DownloadFinishHandler(self):
818 """This handler just tells the server to finish the current download."""
819
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000820 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000821 return False
822
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000823 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000824 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000825 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000826 self.send_header('Cache-Control', 'max-age=0')
827 self.end_headers()
828 return True
829
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000830 def _ReplaceFileData(self, data, query_parameters):
831 """Replaces matching substrings in a file.
832
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000833 If the 'replace_text' URL query parameter is present, it is expected to be
834 of the form old_text:new_text, which indicates that any old_text strings in
835 the file are replaced with new_text. Multiple 'replace_text' parameters may
836 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000837
838 If the parameters are not present, |data| is returned.
839 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000840
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000841 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000842 replace_text_values = query_dict.get('replace_text', [])
843 for replace_text_value in replace_text_values:
844 replace_text_args = replace_text_value.split(':')
845 if len(replace_text_args) != 2:
846 raise ValueError(
847 'replace_text must be of form old_text:new_text. Actual value: %s' %
848 replace_text_value)
849 old_text_b64, new_text_b64 = replace_text_args
850 old_text = base64.urlsafe_b64decode(old_text_b64)
851 new_text = base64.urlsafe_b64decode(new_text_b64)
852 data = data.replace(old_text, new_text)
853 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000854
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000855 def ZipFileHandler(self):
856 """This handler sends the contents of the requested file in compressed form.
857 Can pass in a parameter that specifies that the content length be
858 C - the compressed size (OK),
859 U - the uncompressed size (Non-standard, but handled),
860 S - less than compressed (OK because we keep going),
861 M - larger than compressed but less than uncompressed (an error),
862 L - larger than uncompressed (an error)
863 Example: compressedfiles/Picture_1.doc?C
864 """
865
866 prefix = "/compressedfiles/"
867 if not self.path.startswith(prefix):
868 return False
869
870 # Consume a request body if present.
871 if self.command == 'POST' or self.command == 'PUT' :
872 self.ReadRequestBody()
873
874 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
875
876 if not query in ('C', 'U', 'S', 'M', 'L'):
877 return False
878
879 sub_path = url_path[len(prefix):]
880 entries = sub_path.split('/')
881 file_path = os.path.join(self.server.data_dir, *entries)
882 if os.path.isdir(file_path):
883 file_path = os.path.join(file_path, 'index.html')
884
885 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000886 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000887 self.send_error(404)
888 return True
889
890 f = open(file_path, "rb")
891 data = f.read()
892 uncompressed_len = len(data)
893 f.close()
894
895 # Compress the data.
896 data = zlib.compress(data)
897 compressed_len = len(data)
898
899 content_length = compressed_len
900 if query == 'U':
901 content_length = uncompressed_len
902 elif query == 'S':
903 content_length = compressed_len / 2
904 elif query == 'M':
905 content_length = (compressed_len + uncompressed_len) / 2
906 elif query == 'L':
907 content_length = compressed_len + uncompressed_len
908
909 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000910 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000911 self.send_header('Content-encoding', 'deflate')
912 self.send_header('Connection', 'close')
913 self.send_header('Content-Length', content_length)
914 self.send_header('ETag', '\'' + file_path + '\'')
915 self.end_headers()
916
917 self.wfile.write(data)
918
919 return True
920
initial.commit94958cf2008-07-26 22:42:52 +0000921 def FileHandler(self):
922 """This handler sends the contents of the requested file. Wow, it's like
923 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000924
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000925 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000926 if not self.path.startswith(prefix):
927 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000928 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000929
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000930 def PostOnlyFileHandler(self):
931 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000932
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000933 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000934 if not self.path.startswith(prefix):
935 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000936 return self._FileHandlerHelper(prefix)
937
938 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000939 request_body = ''
940 if self.command == 'POST' or self.command == 'PUT':
941 # Consume a request body if present.
942 request_body = self.ReadRequestBody()
943
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000944 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000945 query_dict = cgi.parse_qs(query)
946
947 expected_body = query_dict.get('expected_body', [])
948 if expected_body and request_body not in expected_body:
949 self.send_response(404)
950 self.end_headers()
951 self.wfile.write('')
952 return True
953
954 expected_headers = query_dict.get('expected_headers', [])
955 for expected_header in expected_headers:
956 header_name, expected_value = expected_header.split(':')
957 if self.headers.getheader(header_name) != expected_value:
958 self.send_response(404)
959 self.end_headers()
960 self.wfile.write('')
961 return True
962
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000963 sub_path = url_path[len(prefix):]
964 entries = sub_path.split('/')
965 file_path = os.path.join(self.server.data_dir, *entries)
966 if os.path.isdir(file_path):
967 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000968
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000969 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000970 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000971 self.send_error(404)
972 return True
973
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000974 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000975 data = f.read()
976 f.close()
977
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000978 data = self._ReplaceFileData(data, query)
979
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000980 old_protocol_version = self.protocol_version
981
initial.commit94958cf2008-07-26 22:42:52 +0000982 # If file.mock-http-headers exists, it contains the headers we
983 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000984 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000985 if os.path.isfile(headers_path):
986 f = open(headers_path, "r")
987
988 # "HTTP/1.1 200 OK"
989 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000990 http_major, http_minor, status_code = re.findall(
991 'HTTP/(\d+).(\d+) (\d+)', response)[0]
992 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000993 self.send_response(int(status_code))
994
995 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000996 header_values = re.findall('(\S+):\s*(.*)', line)
997 if len(header_values) > 0:
998 # "name: value"
999 name, value = header_values[0]
1000 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001001 f.close()
1002 else:
1003 # Could be more generic once we support mime-type sniffing, but for
1004 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001005
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001006 range_header = self.headers.get('Range')
1007 if range_header and range_header.startswith('bytes='):
1008 # Note this doesn't handle all valid byte range_header values (i.e.
1009 # left open ended ones), just enough for what we needed so far.
1010 range_header = range_header[6:].split('-')
1011 start = int(range_header[0])
1012 if range_header[1]:
1013 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001014 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001015 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001016
1017 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001018 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1019 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001020 self.send_header('Content-Range', content_range)
1021 data = data[start: end + 1]
1022 else:
1023 self.send_response(200)
1024
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001025 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001026 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001027 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001028 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001029 self.end_headers()
1030
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001031 if (self.command != 'HEAD'):
1032 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001033
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001034 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001035 return True
1036
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001037 def SetCookieHandler(self):
1038 """This handler just sets a cookie, for testing cookie handling."""
1039
1040 if not self._ShouldHandleRequest("/set-cookie"):
1041 return False
1042
1043 query_char = self.path.find('?')
1044 if query_char != -1:
1045 cookie_values = self.path[query_char + 1:].split('&')
1046 else:
1047 cookie_values = ("",)
1048 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001049 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001050 for cookie_value in cookie_values:
1051 self.send_header('Set-Cookie', '%s' % cookie_value)
1052 self.end_headers()
1053 for cookie_value in cookie_values:
1054 self.wfile.write('%s' % cookie_value)
1055 return True
1056
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001057 def SetManyCookiesHandler(self):
1058 """This handler just sets a given number of cookies, for testing handling
1059 of large numbers of cookies."""
1060
1061 if not self._ShouldHandleRequest("/set-many-cookies"):
1062 return False
1063
1064 query_char = self.path.find('?')
1065 if query_char != -1:
1066 num_cookies = int(self.path[query_char + 1:])
1067 else:
1068 num_cookies = 0
1069 self.send_response(200)
1070 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001071 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001072 self.send_header('Set-Cookie', 'a=')
1073 self.end_headers()
1074 self.wfile.write('%d cookies were sent' % num_cookies)
1075 return True
1076
mattm@chromium.org983fc462012-06-30 00:52:08 +00001077 def ExpectAndSetCookieHandler(self):
1078 """Expects some cookies to be sent, and if they are, sets more cookies.
1079
1080 The expect parameter specifies a required cookie. May be specified multiple
1081 times.
1082 The set parameter specifies a cookie to set if all required cookies are
1083 preset. May be specified multiple times.
1084 The data parameter specifies the response body data to be returned."""
1085
1086 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1087 return False
1088
1089 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1090 query_dict = cgi.parse_qs(query)
1091 cookies = set()
1092 if 'Cookie' in self.headers:
1093 cookie_header = self.headers.getheader('Cookie')
1094 cookies.update([s.strip() for s in cookie_header.split(';')])
1095 got_all_expected_cookies = True
1096 for expected_cookie in query_dict.get('expect', []):
1097 if expected_cookie not in cookies:
1098 got_all_expected_cookies = False
1099 self.send_response(200)
1100 self.send_header('Content-Type', 'text/html')
1101 if got_all_expected_cookies:
1102 for cookie_value in query_dict.get('set', []):
1103 self.send_header('Set-Cookie', '%s' % cookie_value)
1104 self.end_headers()
1105 for data_value in query_dict.get('data', []):
1106 self.wfile.write(data_value)
1107 return True
1108
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001109 def SetHeaderHandler(self):
1110 """This handler sets a response header. Parameters are in the
1111 key%3A%20value&key2%3A%20value2 format."""
1112
1113 if not self._ShouldHandleRequest("/set-header"):
1114 return False
1115
1116 query_char = self.path.find('?')
1117 if query_char != -1:
1118 headers_values = self.path[query_char + 1:].split('&')
1119 else:
1120 headers_values = ("",)
1121 self.send_response(200)
1122 self.send_header('Content-Type', 'text/html')
1123 for header_value in headers_values:
1124 header_value = urllib.unquote(header_value)
1125 (key, value) = header_value.split(': ', 1)
1126 self.send_header(key, value)
1127 self.end_headers()
1128 for header_value in headers_values:
1129 self.wfile.write('%s' % header_value)
1130 return True
1131
initial.commit94958cf2008-07-26 22:42:52 +00001132 def AuthBasicHandler(self):
1133 """This handler tests 'Basic' authentication. It just sends a page with
1134 title 'user/pass' if you succeed."""
1135
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001136 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001137 return False
1138
1139 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001140 expected_password = 'secret'
1141 realm = 'testrealm'
1142 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001143
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001144 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1145 query_params = cgi.parse_qs(query, True)
1146 if 'set-cookie-if-challenged' in query_params:
1147 set_cookie_if_challenged = True
1148 if 'password' in query_params:
1149 expected_password = query_params['password'][0]
1150 if 'realm' in query_params:
1151 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001152
initial.commit94958cf2008-07-26 22:42:52 +00001153 auth = self.headers.getheader('authorization')
1154 try:
1155 if not auth:
1156 raise Exception('no auth')
1157 b64str = re.findall(r'Basic (\S+)', auth)[0]
1158 userpass = base64.b64decode(b64str)
1159 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001160 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001161 raise Exception('wrong password')
1162 except Exception, e:
1163 # Authentication failed.
1164 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001165 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001166 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001167 if set_cookie_if_challenged:
1168 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001169 self.end_headers()
1170 self.wfile.write('<html><head>')
1171 self.wfile.write('<title>Denied: %s</title>' % e)
1172 self.wfile.write('</head><body>')
1173 self.wfile.write('auth=%s<p>' % auth)
1174 self.wfile.write('b64str=%s<p>' % b64str)
1175 self.wfile.write('username: %s<p>' % username)
1176 self.wfile.write('userpass: %s<p>' % userpass)
1177 self.wfile.write('password: %s<p>' % password)
1178 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1179 self.wfile.write('</body></html>')
1180 return True
1181
1182 # Authentication successful. (Return a cachable response to allow for
1183 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001184 old_protocol_version = self.protocol_version
1185 self.protocol_version = "HTTP/1.1"
1186
initial.commit94958cf2008-07-26 22:42:52 +00001187 if_none_match = self.headers.getheader('if-none-match')
1188 if if_none_match == "abc":
1189 self.send_response(304)
1190 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001191 elif url_path.endswith(".gif"):
1192 # Using chrome/test/data/google/logo.gif as the test image
1193 test_image_path = ['google', 'logo.gif']
1194 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1195 if not os.path.isfile(gif_path):
1196 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001197 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001198 return True
1199
1200 f = open(gif_path, "rb")
1201 data = f.read()
1202 f.close()
1203
1204 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001205 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001206 self.send_header('Cache-control', 'max-age=60000')
1207 self.send_header('Etag', 'abc')
1208 self.end_headers()
1209 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001210 else:
1211 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001212 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001213 self.send_header('Cache-control', 'max-age=60000')
1214 self.send_header('Etag', 'abc')
1215 self.end_headers()
1216 self.wfile.write('<html><head>')
1217 self.wfile.write('<title>%s/%s</title>' % (username, password))
1218 self.wfile.write('</head><body>')
1219 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001220 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001221 self.wfile.write('</body></html>')
1222
rvargas@google.com54453b72011-05-19 01:11:11 +00001223 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001224 return True
1225
tonyg@chromium.org75054202010-03-31 22:06:10 +00001226 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001227 """Returns a nonce that's stable per request path for the server's lifetime.
1228 This is a fake implementation. A real implementation would only use a given
1229 nonce a single time (hence the name n-once). However, for the purposes of
1230 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001231
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001232 Args:
1233 force_reset: Iff set, the nonce will be changed. Useful for testing the
1234 "stale" response.
1235 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001236
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001237 if force_reset or not self.server.nonce_time:
1238 self.server.nonce_time = time.time()
1239 return hashlib.md5('privatekey%s%d' %
1240 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001241
1242 def AuthDigestHandler(self):
1243 """This handler tests 'Digest' authentication.
1244
1245 It just sends a page with title 'user/pass' if you succeed.
1246
1247 A stale response is sent iff "stale" is present in the request path.
1248 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001249
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001250 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001251 return False
1252
tonyg@chromium.org75054202010-03-31 22:06:10 +00001253 stale = 'stale' in self.path
1254 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001255 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001256 password = 'secret'
1257 realm = 'testrealm'
1258
1259 auth = self.headers.getheader('authorization')
1260 pairs = {}
1261 try:
1262 if not auth:
1263 raise Exception('no auth')
1264 if not auth.startswith('Digest'):
1265 raise Exception('not digest')
1266 # Pull out all the name="value" pairs as a dictionary.
1267 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1268
1269 # Make sure it's all valid.
1270 if pairs['nonce'] != nonce:
1271 raise Exception('wrong nonce')
1272 if pairs['opaque'] != opaque:
1273 raise Exception('wrong opaque')
1274
1275 # Check the 'response' value and make sure it matches our magic hash.
1276 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001277 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001278 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001279 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001280 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001281 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001282 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1283 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001284 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001285
1286 if pairs['response'] != response:
1287 raise Exception('wrong password')
1288 except Exception, e:
1289 # Authentication failed.
1290 self.send_response(401)
1291 hdr = ('Digest '
1292 'realm="%s", '
1293 'domain="/", '
1294 'qop="auth", '
1295 'algorithm=MD5, '
1296 'nonce="%s", '
1297 'opaque="%s"') % (realm, nonce, opaque)
1298 if stale:
1299 hdr += ', stale="TRUE"'
1300 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001301 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001302 self.end_headers()
1303 self.wfile.write('<html><head>')
1304 self.wfile.write('<title>Denied: %s</title>' % e)
1305 self.wfile.write('</head><body>')
1306 self.wfile.write('auth=%s<p>' % auth)
1307 self.wfile.write('pairs=%s<p>' % pairs)
1308 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1309 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1310 self.wfile.write('</body></html>')
1311 return True
1312
1313 # Authentication successful.
1314 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001315 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001316 self.end_headers()
1317 self.wfile.write('<html><head>')
1318 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1319 self.wfile.write('</head><body>')
1320 self.wfile.write('auth=%s<p>' % auth)
1321 self.wfile.write('pairs=%s<p>' % pairs)
1322 self.wfile.write('</body></html>')
1323
1324 return True
1325
1326 def SlowServerHandler(self):
1327 """Wait for the user suggested time before responding. The syntax is
1328 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001329
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001330 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001331 return False
1332 query_char = self.path.find('?')
1333 wait_sec = 1.0
1334 if query_char >= 0:
1335 try:
davidben05f82202015-03-31 13:48:07 -07001336 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001337 except ValueError:
1338 pass
1339 time.sleep(wait_sec)
1340 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001341 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001342 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001343 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001344 return True
1345
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001346 def ChunkedServerHandler(self):
1347 """Send chunked response. Allows to specify chunks parameters:
1348 - waitBeforeHeaders - ms to wait before sending headers
1349 - waitBetweenChunks - ms to wait between chunks
1350 - chunkSize - size of each chunk in bytes
1351 - chunksNumber - number of chunks
1352 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1353 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001354
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001355 if not self._ShouldHandleRequest("/chunked"):
1356 return False
1357 query_char = self.path.find('?')
1358 chunkedSettings = {'waitBeforeHeaders' : 0,
1359 'waitBetweenChunks' : 0,
1360 'chunkSize' : 5,
1361 'chunksNumber' : 5}
1362 if query_char >= 0:
1363 params = self.path[query_char + 1:].split('&')
1364 for param in params:
1365 keyValue = param.split('=')
1366 if len(keyValue) == 2:
1367 try:
1368 chunkedSettings[keyValue[0]] = int(keyValue[1])
1369 except ValueError:
1370 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001371 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001372 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1373 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001374 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001375 self.send_header('Connection', 'close')
1376 self.send_header('Transfer-Encoding', 'chunked')
1377 self.end_headers()
1378 # Chunked encoding: sending all chunks, then final zero-length chunk and
1379 # then final CRLF.
1380 for i in range(0, chunkedSettings['chunksNumber']):
1381 if i > 0:
1382 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1383 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001384 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001385 self.sendChunkHelp('')
1386 return True
1387
creis@google.com2f4f6a42011-03-25 19:44:19 +00001388 def NoContentHandler(self):
1389 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001390
creis@google.com2f4f6a42011-03-25 19:44:19 +00001391 if not self._ShouldHandleRequest("/nocontent"):
1392 return False
1393 self.send_response(204)
1394 self.end_headers()
1395 return True
1396
initial.commit94958cf2008-07-26 22:42:52 +00001397 def ServerRedirectHandler(self):
1398 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001399 '/server-redirect?http://foo.bar/asdf' to redirect to
1400 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001401
1402 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001403 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001404 return False
1405
1406 query_char = self.path.find('?')
1407 if query_char < 0 or len(self.path) <= query_char + 1:
1408 self.sendRedirectHelp(test_name)
1409 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001410 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001411
1412 self.send_response(301) # moved permanently
1413 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001414 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001415 self.end_headers()
1416 self.wfile.write('<html><head>')
1417 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1418
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001419 return True
initial.commit94958cf2008-07-26 22:42:52 +00001420
naskoe7a0d0d2014-09-29 08:53:05 -07001421 def CrossSiteRedirectHandler(self):
1422 """Sends a server redirect to the given site. The syntax is
1423 '/cross-site/hostname/...' to redirect to //hostname/...
1424 It is used to navigate between different Sites, causing
1425 cross-site/cross-process navigations in the browser."""
1426
1427 test_name = "/cross-site"
1428 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001429 return False
1430
1431 params = urllib.unquote(self.path[(len(test_name) + 1):])
1432 slash = params.find('/')
1433 if slash < 0:
1434 self.sendRedirectHelp(test_name)
1435 return True
1436
1437 host = params[:slash]
1438 path = params[(slash+1):]
1439 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1440
1441 self.send_response(301) # moved permanently
1442 self.send_header('Location', dest)
1443 self.send_header('Content-Type', 'text/html')
1444 self.end_headers()
1445 self.wfile.write('<html><head>')
1446 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1447
1448 return True
1449
initial.commit94958cf2008-07-26 22:42:52 +00001450 def ClientRedirectHandler(self):
1451 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001452 '/client-redirect?http://foo.bar/asdf' to redirect to
1453 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001454
1455 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001456 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001457 return False
1458
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001459 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001460 if query_char < 0 or len(self.path) <= query_char + 1:
1461 self.sendRedirectHelp(test_name)
1462 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001463 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001464
1465 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001466 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001467 self.end_headers()
1468 self.wfile.write('<html><head>')
1469 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1470 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1471
1472 return True
1473
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001474 def GetSSLSessionCacheHandler(self):
1475 """Send a reply containing a log of the session cache operations."""
1476
1477 if not self._ShouldHandleRequest('/ssl-session-cache'):
1478 return False
1479
1480 self.send_response(200)
1481 self.send_header('Content-Type', 'text/plain')
1482 self.end_headers()
1483 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001484 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001485 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001486 self.wfile.write('Pass --https-record-resume in order to use' +
1487 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001488 return True
1489
1490 for (action, sessionID) in log:
1491 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001492 return True
1493
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001494 def SSLManySmallRecords(self):
1495 """Sends a reply consisting of a variety of small writes. These will be
1496 translated into a series of small SSL records when used over an HTTPS
1497 server."""
1498
1499 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1500 return False
1501
1502 self.send_response(200)
1503 self.send_header('Content-Type', 'text/plain')
1504 self.end_headers()
1505
1506 # Write ~26K of data, in 1350 byte chunks
1507 for i in xrange(20):
1508 self.wfile.write('*' * 1350)
1509 self.wfile.flush()
1510 return True
1511
agl@chromium.org04700be2013-03-02 18:40:41 +00001512 def GetChannelID(self):
1513 """Send a reply containing the hashed ChannelID that the client provided."""
1514
1515 if not self._ShouldHandleRequest('/channel-id'):
1516 return False
1517
1518 self.send_response(200)
1519 self.send_header('Content-Type', 'text/plain')
1520 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001521 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001522 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1523 return True
1524
nharper08eae822016-01-25 15:54:14 -08001525 def GetTokenBindingEKM(self):
1526 """Send a reply containing the EKM value for token binding from the TLS
1527 layer."""
1528
1529 if not self._ShouldHandleRequest('/tokbind-ekm'):
1530 return False
1531
1532 ekm = self.server.tlsConnection.exportKeyingMaterial(
1533 "EXPORTER-Token-Binding", "", False, 32)
1534 self.send_response(200)
1535 self.send_header('Content-Type', 'application/octet-stream')
1536 self.end_headers()
1537 self.wfile.write(ekm)
1538 return True
1539
nharpercb1adc32016-03-30 16:05:48 -07001540 def ForwardTokenBindingHeader(self):
nharpere758cd12016-07-13 17:49:36 -07001541 """Send a redirect that sets the Include-Referred-Token-Binding-ID
nharpercb1adc32016-03-30 16:05:48 -07001542 header."""
1543
1544 test_name = '/forward-tokbind'
1545 if not self._ShouldHandleRequest(test_name):
1546 return False
1547
1548 query_char = self.path.find('?')
1549 if query_char < 0 or len(self.path) <= query_char + 1:
1550 self.sendRedirectHelp(test_name)
1551 return True
1552 dest = urllib.unquote(self.path[query_char + 1:])
1553
1554 self.send_response(302)
1555 self.send_header('Location', dest)
nharpere758cd12016-07-13 17:49:36 -07001556 self.send_header('Include-Referred-Token-Binding-ID', 'true')
nharpercb1adc32016-03-30 16:05:48 -07001557 self.end_headers()
1558 return True
1559
pneubeckfd4f0442015-08-07 04:55:10 -07001560 def GetClientCert(self):
1561 """Send a reply whether a client certificate was provided."""
1562
1563 if not self._ShouldHandleRequest('/client-cert'):
1564 return False
1565
1566 self.send_response(200)
1567 self.send_header('Content-Type', 'text/plain')
1568 self.end_headers()
1569
1570 cert_chain = self.server.tlsConnection.session.clientCertChain
1571 if cert_chain != None:
1572 self.wfile.write('got client cert with fingerprint: ' +
1573 cert_chain.getFingerprint())
1574 else:
1575 self.wfile.write('got no client cert')
1576 return True
1577
davidben599e7e72014-09-03 16:19:09 -07001578 def ClientCipherListHandler(self):
1579 """Send a reply containing the cipher suite list that the client
1580 provided. Each cipher suite value is serialized in decimal, followed by a
1581 newline."""
1582
1583 if not self._ShouldHandleRequest('/client-cipher-list'):
1584 return False
1585
1586 self.send_response(200)
1587 self.send_header('Content-Type', 'text/plain')
1588 self.end_headers()
1589
davidben11682512014-10-06 21:09:11 -07001590 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1591 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001592 return True
1593
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001594 def CloseSocketHandler(self):
1595 """Closes the socket without sending anything."""
1596
1597 if not self._ShouldHandleRequest('/close-socket'):
1598 return False
1599
1600 self.wfile.close()
1601 return True
1602
initial.commit94958cf2008-07-26 22:42:52 +00001603 def DefaultResponseHandler(self):
1604 """This is the catch-all response handler for requests that aren't handled
1605 by one of the special handlers above.
1606 Note that we specify the content-length as without it the https connection
1607 is not closed properly (and the browser keeps expecting data)."""
1608
1609 contents = "Default response given for path: " + self.path
1610 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001611 self.send_header('Content-Type', 'text/html')
1612 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001613 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001614 if (self.command != 'HEAD'):
1615 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001616 return True
1617
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001618 def RedirectConnectHandler(self):
1619 """Sends a redirect to the CONNECT request for www.redirect.com. This
1620 response is not specified by the RFC, so the browser should not follow
1621 the redirect."""
1622
1623 if (self.path.find("www.redirect.com") < 0):
1624 return False
1625
1626 dest = "http://www.destination.com/foo.js"
1627
1628 self.send_response(302) # moved temporarily
1629 self.send_header('Location', dest)
1630 self.send_header('Connection', 'close')
1631 self.end_headers()
1632 return True
1633
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001634 def ServerAuthConnectHandler(self):
1635 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1636 response doesn't make sense because the proxy server cannot request
1637 server authentication."""
1638
1639 if (self.path.find("www.server-auth.com") < 0):
1640 return False
1641
1642 challenge = 'Basic realm="WallyWorld"'
1643
1644 self.send_response(401) # unauthorized
1645 self.send_header('WWW-Authenticate', challenge)
1646 self.send_header('Connection', 'close')
1647 self.end_headers()
1648 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001649
1650 def DefaultConnectResponseHandler(self):
1651 """This is the catch-all response handler for CONNECT requests that aren't
1652 handled by one of the special handlers above. Real Web servers respond
1653 with 400 to CONNECT requests."""
1654
1655 contents = "Your client has issued a malformed or illegal request."
1656 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001657 self.send_header('Content-Type', 'text/html')
1658 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001659 self.end_headers()
1660 self.wfile.write(contents)
1661 return True
1662
initial.commit94958cf2008-07-26 22:42:52 +00001663 # called by the redirect handling function when there is no parameter
1664 def sendRedirectHelp(self, redirect_name):
1665 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001666 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001667 self.end_headers()
1668 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1669 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1670 self.wfile.write('</body></html>')
1671
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001672 # called by chunked handling function
1673 def sendChunkHelp(self, chunk):
1674 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1675 self.wfile.write('%X\r\n' % len(chunk))
1676 self.wfile.write(chunk)
1677 self.wfile.write('\r\n')
1678
akalin@chromium.org154bb132010-11-12 02:20:27 +00001679
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001680class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001681 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001682 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001683 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001684 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001685 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001686 testserver_base.BasePageHandler.__init__(self, request, client_address,
1687 socket_server, [], handlers, [],
1688 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001689
1690 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001691 if self._ShouldHandleRequest("/ocsp"):
1692 response = self.ocsp_response
1693 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1694 response = self.ocsp_response_intermediate
1695 else:
mattm10ede842016-11-29 11:57:16 -08001696 return False
1697 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001698 self.send_response(200)
1699 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001700 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001701 self.end_headers()
1702
Matt Mueller55aef642018-05-02 18:53:57 +00001703 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001704
mattm10ede842016-11-29 11:57:16 -08001705 def CaIssuersResponse(self):
1706 if not self._ShouldHandleRequest("/ca_issuers"):
1707 return False
1708 print 'handling ca_issuers request'
1709 self.send_response(200)
1710 self.send_header('Content-Type', 'application/pkix-cert')
1711 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1712 self.end_headers()
1713
1714 self.wfile.write(self.ca_issuers_response)
1715
mattm@chromium.org830a3712012-11-07 23:00:07 +00001716
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001717class TCPEchoHandler(SocketServer.BaseRequestHandler):
1718 """The RequestHandler class for TCP echo server.
1719
1720 It is instantiated once per connection to the server, and overrides the
1721 handle() method to implement communication to the client.
1722 """
1723
1724 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001725 """Handles the request from the client and constructs a response."""
1726
1727 data = self.request.recv(65536).strip()
1728 # Verify the "echo request" message received from the client. Send back
1729 # "echo response" message if "echo request" message is valid.
1730 try:
1731 return_data = echo_message.GetEchoResponseData(data)
1732 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001733 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001734 except ValueError:
1735 return
1736
1737 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001738
1739
1740class UDPEchoHandler(SocketServer.BaseRequestHandler):
1741 """The RequestHandler class for UDP echo server.
1742
1743 It is instantiated once per connection to the server, and overrides the
1744 handle() method to implement communication to the client.
1745 """
1746
1747 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001748 """Handles the request from the client and constructs a response."""
1749
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001750 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001751 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001752 # Verify the "echo request" message received from the client. Send back
1753 # "echo response" message if "echo request" message is valid.
1754 try:
1755 return_data = echo_message.GetEchoResponseData(data)
1756 if not return_data:
1757 return
1758 except ValueError:
1759 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001760 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001761
1762
Adam Rice9476b8c2018-08-02 15:28:43 +00001763class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1764 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1765 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001766 """
1767
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001768 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001769
bashi@chromium.org33233532012-09-08 17:37:24 +00001770 def _start_read_write(self, sock):
1771 sock.setblocking(0)
1772 self.request.setblocking(0)
1773 rlist = [self.request, sock]
1774 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001775 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001776 if errors:
1777 self.send_response(500)
1778 self.end_headers()
1779 return
1780 for s in ready_sockets:
1781 received = s.recv(1024)
1782 if len(received) == 0:
1783 return
1784 if s == self.request:
1785 other = sock
1786 else:
1787 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001788 # This will lose data if the kernel write buffer fills up.
1789 # TODO(ricea): Correctly use the return value to track how much was
1790 # written and buffer the rest. Use select to determine when the socket
1791 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001792 other.send(received)
1793
1794 def _do_common_method(self):
1795 url = urlparse.urlparse(self.path)
1796 port = url.port
1797 if not port:
1798 if url.scheme == 'http':
1799 port = 80
1800 elif url.scheme == 'https':
1801 port = 443
1802 if not url.hostname or not port:
1803 self.send_response(400)
1804 self.end_headers()
1805 return
1806
1807 if len(url.path) == 0:
1808 path = '/'
1809 else:
1810 path = url.path
1811 if len(url.query) > 0:
1812 path = '%s?%s' % (url.path, url.query)
1813
1814 sock = None
1815 try:
1816 sock = socket.create_connection((url.hostname, port))
1817 sock.send('%s %s %s\r\n' % (
1818 self.command, path, self.protocol_version))
1819 for header in self.headers.headers:
1820 header = header.strip()
1821 if (header.lower().startswith('connection') or
1822 header.lower().startswith('proxy')):
1823 continue
1824 sock.send('%s\r\n' % header)
1825 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001826 # This is wrong: it will pass through connection-level headers and
1827 # misbehave on connection reuse. The only reason it works at all is that
1828 # our test servers have never supported connection reuse.
1829 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001830 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001831 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001832 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001833 self.send_response(500)
1834 self.end_headers()
1835 finally:
1836 if sock is not None:
1837 sock.close()
1838
1839 def do_CONNECT(self):
1840 try:
1841 pos = self.path.rfind(':')
1842 host = self.path[:pos]
1843 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001844 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001845 self.send_response(400)
1846 self.end_headers()
1847
Adam Rice9476b8c2018-08-02 15:28:43 +00001848 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001849 host = "127.0.0.1"
1850
Adam Rice54443aa2018-06-06 00:11:54 +00001851 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001852 try:
1853 sock = socket.create_connection((host, port))
1854 self.send_response(200, 'Connection established')
1855 self.end_headers()
1856 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001857 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001858 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001859 self.send_response(500)
1860 self.end_headers()
1861 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001862 if sock is not None:
1863 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001864
1865 def do_GET(self):
1866 self._do_common_method()
1867
1868 def do_HEAD(self):
1869 self._do_common_method()
1870
Adam Rice9476b8c2018-08-02 15:28:43 +00001871class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1872 """A request handler that behaves as a proxy server which requires
1873 basic authentication.
1874 """
1875
1876 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1877
1878 def parse_request(self):
1879 """Overrides parse_request to check credential."""
1880
1881 if not ProxyRequestHandler.parse_request(self):
1882 return False
1883
1884 auth = self.headers.getheader('Proxy-Authorization')
1885 if auth != self._AUTH_CREDENTIAL:
1886 self.send_response(407)
1887 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1888 self.end_headers()
1889 return False
1890
1891 return True
1892
bashi@chromium.org33233532012-09-08 17:37:24 +00001893
mattm@chromium.org830a3712012-11-07 23:00:07 +00001894class ServerRunner(testserver_base.TestServerRunner):
1895 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001896
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 def __init__(self):
1898 super(ServerRunner, self).__init__()
1899 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001900
mattm@chromium.org830a3712012-11-07 23:00:07 +00001901 def __make_data_dir(self):
1902 if self.options.data_dir:
1903 if not os.path.isdir(self.options.data_dir):
1904 raise testserver_base.OptionError('specified data dir not found: ' +
1905 self.options.data_dir + ' exiting...')
1906 my_data_dir = self.options.data_dir
1907 else:
1908 # Create the default path to our data dir, relative to the exe dir.
1909 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1910 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001911
mattm@chromium.org830a3712012-11-07 23:00:07 +00001912 #TODO(ibrar): Must use Find* funtion defined in google\tools
1913 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001914
mattm@chromium.org830a3712012-11-07 23:00:07 +00001915 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001916
Matt Mueller55aef642018-05-02 18:53:57 +00001917 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1918 if states_option is None:
1919 return None, None, None
1920
1921 ocsp_states = list()
1922 for ocsp_state_arg in states_option.split(':'):
1923 if ocsp_state_arg == 'ok':
1924 ocsp_state = minica.OCSP_STATE_GOOD
1925 elif ocsp_state_arg == 'revoked':
1926 ocsp_state = minica.OCSP_STATE_REVOKED
1927 elif ocsp_state_arg == 'invalid':
1928 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1929 elif ocsp_state_arg == 'unauthorized':
1930 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1931 elif ocsp_state_arg == 'unknown':
1932 ocsp_state = minica.OCSP_STATE_UNKNOWN
1933 elif ocsp_state_arg == 'later':
1934 ocsp_state = minica.OCSP_STATE_TRY_LATER
1935 elif ocsp_state_arg == 'invalid_data':
1936 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1937 elif ocsp_state_arg == "mismatched_serial":
1938 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1939 else:
1940 raise testserver_base.OptionError('unknown OCSP status: ' +
1941 ocsp_state_arg)
1942 ocsp_states.append(ocsp_state)
1943
1944 if len(ocsp_states) > 1:
1945 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1946 raise testserver_base.OptionError('Multiple OCSP responses '
1947 'incompatible with states ' + str(ocsp_states))
1948
1949 ocsp_dates = list()
1950 for ocsp_date_arg in date_option.split(':'):
1951 if ocsp_date_arg == 'valid':
1952 ocsp_date = minica.OCSP_DATE_VALID
1953 elif ocsp_date_arg == 'old':
1954 ocsp_date = minica.OCSP_DATE_OLD
1955 elif ocsp_date_arg == 'early':
1956 ocsp_date = minica.OCSP_DATE_EARLY
1957 elif ocsp_date_arg == 'long':
1958 ocsp_date = minica.OCSP_DATE_LONG
1959 elif ocsp_date_arg == 'longer':
1960 ocsp_date = minica.OCSP_DATE_LONGER
1961 else:
1962 raise testserver_base.OptionError('unknown OCSP date: ' +
1963 ocsp_date_arg)
1964 ocsp_dates.append(ocsp_date)
1965
1966 if len(ocsp_states) != len(ocsp_dates):
1967 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1968 'count')
1969
1970 ocsp_produced = None
1971 if produced_option == 'valid':
1972 ocsp_produced = minica.OCSP_PRODUCED_VALID
1973 elif produced_option == 'before':
1974 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1975 elif produced_option == 'after':
1976 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1977 else:
1978 raise testserver_base.OptionError('unknown OCSP produced: ' +
1979 produced_option)
1980
1981 return ocsp_states, ocsp_dates, ocsp_produced
1982
mattm@chromium.org830a3712012-11-07 23:00:07 +00001983 def create_server(self, server_data):
1984 port = self.options.port
1985 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001986
Adam Rice54443aa2018-06-06 00:11:54 +00001987 logging.basicConfig()
1988
estark21667d62015-04-08 21:00:16 -07001989 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1990 # will result in a call to |getaddrinfo|, which fails with "nodename
1991 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001992 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001993 if self.options.server_type == SERVER_WEBSOCKET and \
1994 host == "localhost" and \
1995 port == 0:
1996 host = "127.0.0.1"
1997
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001998 # Construct the subjectAltNames for any ad-hoc generated certificates.
1999 # As host can be either a DNS name or IP address, attempt to determine
2000 # which it is, so it can be placed in the appropriate SAN.
2001 dns_sans = None
2002 ip_sans = None
2003 ip = None
2004 try:
2005 ip = socket.inet_aton(host)
2006 ip_sans = [ip]
2007 except socket.error:
2008 pass
2009 if ip is None:
2010 dns_sans = [host]
2011
mattm@chromium.org830a3712012-11-07 23:00:07 +00002012 if self.options.server_type == SERVER_HTTP:
2013 if self.options.https:
2014 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08002015 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002016 if self.options.cert_and_key_file:
2017 if not os.path.isfile(self.options.cert_and_key_file):
2018 raise testserver_base.OptionError(
2019 'specified server cert file not found: ' +
2020 self.options.cert_and_key_file + ' exiting...')
2021 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08002022 elif self.options.aia_intermediate:
2023 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
2024 print ('AIA server started on %s:%d...' %
2025 (host, self.__ocsp_server.server_port))
2026
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002027 ocsp_server_port = self.__ocsp_server.server_port
2028 if self.options.ocsp_proxy_port_number != 0:
2029 ocsp_server_port = self.options.ocsp_proxy_port_number
2030 server_data['ocsp_port'] = self.__ocsp_server.server_port
2031
mattm10ede842016-11-29 11:57:16 -08002032 (pem_cert_and_key, intermediate_cert_der) = \
2033 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00002034 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002035 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002036 ca_issuers_url =
2037 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08002038 serial = self.options.cert_serial)
2039
2040 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00002041 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08002042 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002043 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002044 # generate a new certificate and run an OCSP server for it.
2045 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002046 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00002047 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002048
Matt Mueller55aef642018-05-02 18:53:57 +00002049 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
2050 self.options.ocsp,
2051 self.options.ocsp_date,
2052 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002053
Matt Mueller55aef642018-05-02 18:53:57 +00002054 (ocsp_intermediate_states, ocsp_intermediate_dates,
2055 ocsp_intermediate_produced) = self.__parse_ocsp_options(
2056 self.options.ocsp_intermediate,
2057 self.options.ocsp_intermediate_date,
2058 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002059
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002060 ocsp_server_port = self.__ocsp_server.server_port
2061 if self.options.ocsp_proxy_port_number != 0:
2062 ocsp_server_port = self.options.ocsp_proxy_port_number
2063 server_data['ocsp_port'] = self.__ocsp_server.server_port
2064
Matt Mueller55aef642018-05-02 18:53:57 +00002065 pem_cert_and_key, (ocsp_der,
2066 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00002067 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002068 ip_sans = ip_sans,
2069 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002070 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07002071 ocsp_states = ocsp_states,
2072 ocsp_dates = ocsp_dates,
2073 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00002074 ocsp_intermediate_url = (
2075 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
2076 if ocsp_intermediate_states else None),
2077 ocsp_intermediate_states = ocsp_intermediate_states,
2078 ocsp_intermediate_dates = ocsp_intermediate_dates,
2079 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002080 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002081
davidben3e2564a2014-11-07 18:51:00 -08002082 if self.options.ocsp_server_unavailable:
2083 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00002084 self.__ocsp_server.ocsp_response_intermediate = \
2085 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08002086 else:
2087 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00002088 self.__ocsp_server.ocsp_response_intermediate = \
2089 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08002090 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002091
2092 for ca_cert in self.options.ssl_client_ca:
2093 if not os.path.isfile(ca_cert):
2094 raise testserver_base.OptionError(
2095 'specified trusted client CA file not found: ' + ca_cert +
2096 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002097
2098 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002099 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00002100 # TODO(mattm): Staple the intermediate response too (if applicable,
2101 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08002102 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002103
mattm@chromium.org830a3712012-11-07 23:00:07 +00002104 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2105 self.options.ssl_client_auth,
2106 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002107 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002108 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002109 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002110 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002111 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002112 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002113 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002114 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002115 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002116 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002117 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002118 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002119 self.options.alert_after_handshake,
2120 self.options.disable_channel_id,
2121 self.options.disable_extended_master_secret,
2122 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002123 print 'HTTPS server started on https://%s:%d...' % \
2124 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 else:
2126 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002127 print 'HTTP server started on http://%s:%d...' % \
2128 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002129
2130 server.data_dir = self.__make_data_dir()
2131 server.file_root_url = self.options.file_root_url
2132 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002133 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2135 # is required to work correctly. It should be fixed from pywebsocket side.
2136 os.chdir(self.__make_data_dir())
2137 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002138 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002140 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002141 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002142 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2143 if not os.path.isfile(key_path):
2144 raise testserver_base.OptionError(
2145 'specified server cert file not found: ' +
2146 self.options.cert_and_key_file + ' exiting...')
2147 websocket_options.private_key = key_path
2148 websocket_options.certificate = key_path
2149
mattm@chromium.org830a3712012-11-07 23:00:07 +00002150 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002151 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002152 websocket_options.tls_client_auth = True
2153 if len(self.options.ssl_client_ca) != 1:
2154 raise testserver_base.OptionError(
2155 'one trusted client CA file should be specified')
2156 if not os.path.isfile(self.options.ssl_client_ca[0]):
2157 raise testserver_base.OptionError(
2158 'specified trusted client CA file not found: ' +
2159 self.options.ssl_client_ca[0] + ' exiting...')
2160 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002161 print 'Trying to start websocket server on %s://%s:%d...' % \
2162 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002163 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002164 print 'WebSocket server started on %s://%s:%d...' % \
2165 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002166 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002167 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002168 elif self.options.server_type == SERVER_TCP_ECHO:
2169 # Used for generating the key (randomly) that encodes the "echo request"
2170 # message.
2171 random.seed()
2172 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002173 print 'Echo TCP 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_UDP_ECHO:
2176 # Used for generating the key (randomly) that encodes the "echo request"
2177 # message.
2178 random.seed()
2179 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002180 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002181 server_data['port'] = server.server_port
Adam Rice9476b8c2018-08-02 15:28:43 +00002182 elif self.options.server_type == SERVER_PROXY:
2183 ProxyRequestHandler.redirect_connect_to_localhost = \
2184 self.options.redirect_connect_to_localhost
2185 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
2186 print 'Proxy server started on port %d...' % server.server_port
2187 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002188 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00002189 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002190 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00002191 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002192 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002193 server_data['port'] = server.server_port
2194 elif self.options.server_type == SERVER_FTP:
2195 my_data_dir = self.__make_data_dir()
2196
2197 # Instantiate a dummy authorizer for managing 'virtual' users
2198 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2199
xleng9d4c45f2015-05-04 16:26:12 -07002200 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002201 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2202
xleng9d4c45f2015-05-04 16:26:12 -07002203 # Define a read-only anonymous user unless disabled
2204 if not self.options.no_anonymous_ftp_user:
2205 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002206
2207 # Instantiate FTP handler class
2208 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2209 ftp_handler.authorizer = authorizer
2210
2211 # Define a customized banner (string returned when client connects)
2212 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2213 pyftpdlib.ftpserver.__ver__)
2214
2215 # Instantiate FTP server class and listen to address:port
2216 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2217 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002218 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002219 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002220 raise testserver_base.OptionError('unknown server type' +
2221 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002222
mattm@chromium.org830a3712012-11-07 23:00:07 +00002223 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002224
mattm@chromium.org830a3712012-11-07 23:00:07 +00002225 def run_server(self):
2226 if self.__ocsp_server:
2227 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002228
mattm@chromium.org830a3712012-11-07 23:00:07 +00002229 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002230
mattm@chromium.org830a3712012-11-07 23:00:07 +00002231 if self.__ocsp_server:
2232 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002233
mattm@chromium.org830a3712012-11-07 23:00:07 +00002234 def add_options(self):
2235 testserver_base.TestServerRunner.add_options(self)
2236 self.option_parser.add_option('-f', '--ftp', action='store_const',
2237 const=SERVER_FTP, default=SERVER_HTTP,
2238 dest='server_type',
2239 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002240 self.option_parser.add_option('--tcp-echo', action='store_const',
2241 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2242 dest='server_type',
2243 help='start up a tcp echo server.')
2244 self.option_parser.add_option('--udp-echo', action='store_const',
2245 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2246 dest='server_type',
2247 help='start up a udp echo server.')
Adam Rice9476b8c2018-08-02 15:28:43 +00002248 self.option_parser.add_option('--proxy', action='store_const',
2249 const=SERVER_PROXY,
2250 default=SERVER_HTTP, dest='server_type',
2251 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002252 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2253 const=SERVER_BASIC_AUTH_PROXY,
2254 default=SERVER_HTTP, dest='server_type',
2255 help='start up a proxy server which requires '
2256 'basic authentication.')
2257 self.option_parser.add_option('--websocket', action='store_const',
2258 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2259 dest='server_type',
2260 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002261 self.option_parser.add_option('--https', action='store_true',
2262 dest='https', help='Specify that https '
2263 'should be used.')
2264 self.option_parser.add_option('--cert-and-key-file',
2265 dest='cert_and_key_file', help='specify the '
2266 'path to the file containing the certificate '
2267 'and private key for the server in PEM '
2268 'format')
mattm10ede842016-11-29 11:57:16 -08002269 self.option_parser.add_option('--aia-intermediate', action='store_true',
2270 dest='aia_intermediate',
2271 help='generate a certificate chain that '
2272 'requires AIA cert fetching, and run a '
2273 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002274 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2275 help='The type of OCSP response generated '
2276 'for the automatically generated '
2277 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002278 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2279 default='valid', help='The validity of the '
2280 'range between thisUpdate and nextUpdate')
2281 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2282 default='valid', help='producedAt relative '
2283 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002284 self.option_parser.add_option('--ocsp-intermediate',
2285 dest='ocsp_intermediate', default=None,
2286 help='If specified, the automatically '
2287 'generated chain will include an '
2288 'intermediate certificate with this type '
2289 'of OCSP response (see docs for --ocsp)')
2290 self.option_parser.add_option('--ocsp-intermediate-date',
2291 dest='ocsp_intermediate_date',
2292 default='valid', help='The validity of the '
2293 'range between thisUpdate and nextUpdate')
2294 self.option_parser.add_option('--ocsp-intermediate-produced',
2295 dest='ocsp_intermediate_produced',
2296 default='valid', help='producedAt relative '
2297 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002298 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2299 default=0, type=int,
2300 help='If non-zero then the generated '
2301 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002302 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2303 default="127.0.0.1",
2304 help='The generated certificate will have '
2305 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002306 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2307 default='0', type='int',
2308 help='If nonzero, certain TLS connections '
2309 'will be aborted in order to test version '
2310 'fallback. 1 means all TLS versions will be '
2311 'aborted. 2 means TLS 1.1 or higher will be '
2312 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002313 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002314 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002315 self.option_parser.add_option('--tls-intolerance-type',
2316 dest='tls_intolerance_type',
2317 default="alert",
2318 help='Controls how the server reacts to a '
2319 'TLS version it is intolerant to. Valid '
2320 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002321 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2322 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002323 default='',
2324 help='Base64 encoded SCT list. If set, '
2325 'server will respond with a '
2326 'signed_certificate_timestamp TLS extension '
2327 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002328 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2329 default=False, const=True,
2330 action='store_const',
2331 help='If given, TLS_FALLBACK_SCSV support '
2332 'will be enabled. This causes the server to '
2333 'reject fallback connections from compatible '
2334 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002335 self.option_parser.add_option('--staple-ocsp-response',
2336 dest='staple_ocsp_response',
2337 default=False, action='store_true',
2338 help='If set, server will staple the OCSP '
2339 'response whenever OCSP is on and the client '
2340 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002341 self.option_parser.add_option('--https-record-resume',
2342 dest='record_resume', const=True,
2343 default=False, action='store_const',
2344 help='Record resumption cache events rather '
2345 'than resuming as normal. Allows the use of '
2346 'the /ssl-session-cache request')
2347 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2348 help='Require SSL client auth on every '
2349 'connection.')
2350 self.option_parser.add_option('--ssl-client-ca', action='append',
2351 default=[], help='Specify that the client '
2352 'certificate request should include the CA '
2353 'named in the subject of the DER-encoded '
2354 'certificate contained in the specified '
2355 'file. This option may appear multiple '
2356 'times, indicating multiple CA names should '
2357 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002358 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2359 default=[], help='Specify that the client '
2360 'certificate request should include the '
2361 'specified certificate_type value. This '
2362 'option may appear multiple times, '
2363 'indicating multiple values should be send '
2364 'in the request. Valid values are '
2365 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2366 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002367 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2368 help='Specify the bulk encryption '
2369 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002370 'SSL server. Valid values are "aes128gcm", '
2371 '"aes256", "aes128", "3des", "rc4". If '
2372 'omitted, all algorithms will be used. This '
2373 'option may appear multiple times, '
2374 'indicating multiple algorithms should be '
2375 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002376 self.option_parser.add_option('--ssl-key-exchange', action='append',
2377 help='Specify the key exchange algorithm(s)'
2378 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002379 'Valid values are "rsa", "dhe_rsa", '
2380 '"ecdhe_rsa". If omitted, all algorithms '
2381 'will be used. This option may appear '
2382 'multiple times, indicating multiple '
2383 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002384 self.option_parser.add_option('--alpn-protocols', action='append',
2385 help='Specify the list of ALPN protocols. '
2386 'The server will not send an ALPN response '
2387 'if this list does not overlap with the '
2388 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002389 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002390 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002391 'an NPN response. The server will not'
2392 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002393 self.option_parser.add_option('--file-root-url', default='/files/',
2394 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002395 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2396 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2397 dest='ws_basic_auth',
2398 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002399 self.option_parser.add_option('--ocsp-server-unavailable',
2400 dest='ocsp_server_unavailable',
2401 default=False, action='store_true',
2402 help='If set, the OCSP server will return '
2403 'a tryLater status rather than the actual '
2404 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002405 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2406 type='int', dest='ocsp_proxy_port_number',
2407 help='Port allocated for OCSP proxy '
2408 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002409 self.option_parser.add_option('--alert-after-handshake',
2410 dest='alert_after_handshake',
2411 default=False, action='store_true',
2412 help='If set, the server will send a fatal '
2413 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002414 self.option_parser.add_option('--no-anonymous-ftp-user',
2415 dest='no_anonymous_ftp_user',
2416 default=False, action='store_true',
2417 help='If set, the FTP server will not create '
2418 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002419 self.option_parser.add_option('--disable-channel-id', action='store_true')
2420 self.option_parser.add_option('--disable-extended-master-secret',
2421 action='store_true')
2422 self.option_parser.add_option('--token-binding-params', action='append',
2423 default=[], type='int')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002424 self.option_parser.add_option('--redirect-connect-to-localhost',
2425 dest='redirect_connect_to_localhost',
2426 default=False, action='store_true',
2427 help='If set, the Proxy server will connect '
2428 'to localhost instead of the requested URL '
2429 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002430
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002431
initial.commit94958cf2008-07-26 22:42:52 +00002432if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002433 sys.exit(ServerRunner().main())