blob: 648082e7dad99ed6106d80874b09b942093e86ce [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,
Nick Harper4a22b722018-09-28 20:33:26 +0000164 alert_after_handshake, disable_channel_id, disable_ems):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000165 self.cert_chain = tlslite.api.X509CertChain()
166 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000167 # Force using only python implementation - otherwise behavior is different
168 # depending on whether m2crypto Python module is present (error is thrown
169 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
170 # the hood.
171 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
172 private=True,
173 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000174 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000175 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000176 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700177 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000178 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000179 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000180 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000181
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000182 if ssl_client_auth:
183 for ca_file in ssl_client_cas:
184 s = open(ca_file).read()
185 x509 = tlslite.api.X509()
186 x509.parse(s)
187 self.ssl_client_cas.append(x509.subject)
188
189 for cert_type in ssl_client_cert_types:
190 self.ssl_client_cert_types.append({
191 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800196 # Enable SSLv3 for testing purposes.
197 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000202 if tls_intolerant != 0:
203 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
204 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700205 if alert_after_handshake:
206 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700207 if disable_channel_id:
208 self.ssl_handshake_settings.enableChannelID = False
209 if disable_ems:
210 self.ssl_handshake_settings.enableExtendedMasterSecret = False
bnc5fb33bd2016-08-05 12:09:21 -0700211 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000212
rsleevi8146efa2015-03-16 12:31:24 -0700213 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000214 # If record_resume_info is true then we'll replace the session cache with
215 # an object that records the lookups and inserts that it sees.
216 self.session_cache = RecordingSSLSessionCache()
217 else:
218 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000219 testserver_base.StoppableHTTPServer.__init__(self,
220 server_address,
221 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000222
223 def handshake(self, tlsConnection):
224 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000225
initial.commit94958cf2008-07-26 22:42:52 +0000226 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000227 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000228 tlsConnection.handshakeServer(certChain=self.cert_chain,
229 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000230 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000231 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000232 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000233 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000234 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700235 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000236 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000237 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000238 fallbackSCSV=self.fallback_scsv_enabled,
239 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000240 tlsConnection.ignoreAbruptClose = True
241 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000242 except tlslite.api.TLSAbruptCloseError:
243 # Ignore abrupt close.
244 return True
initial.commit94958cf2008-07-26 22:42:52 +0000245 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000246 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000247 return False
248
akalin@chromium.org154bb132010-11-12 02:20:27 +0000249
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000250class FTPServer(testserver_base.ClientRestrictingServerMixIn,
251 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000252 """This is a specialization of FTPServer that adds client verification."""
253
254 pass
255
256
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000257class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
258 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000259 """A TCP echo server that echoes back what it has received."""
260
261 def server_bind(self):
262 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000263
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000264 SocketServer.TCPServer.server_bind(self)
265 host, port = self.socket.getsockname()[:2]
266 self.server_name = socket.getfqdn(host)
267 self.server_port = port
268
269 def serve_forever(self):
270 self.stop = False
271 self.nonce_time = None
272 while not self.stop:
273 self.handle_request()
274 self.socket.close()
275
276
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000277class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
278 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000279 """A UDP echo server that echoes back what it has received."""
280
281 def server_bind(self):
282 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000283
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000284 SocketServer.UDPServer.server_bind(self)
285 host, port = self.socket.getsockname()[:2]
286 self.server_name = socket.getfqdn(host)
287 self.server_port = port
288
289 def serve_forever(self):
290 self.stop = False
291 self.nonce_time = None
292 while not self.stop:
293 self.handle_request()
294 self.socket.close()
295
296
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000297class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000298 # Class variables to allow for persistence state between page handler
299 # invocations
300 rst_limits = {}
301 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000302
303 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000304 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000305 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000306 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000307 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000308 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000309 self.NoCacheMaxAgeTimeHandler,
310 self.NoCacheTimeHandler,
311 self.CacheTimeHandler,
312 self.CacheExpiresHandler,
313 self.CacheProxyRevalidateHandler,
314 self.CachePrivateHandler,
315 self.CachePublicHandler,
316 self.CacheSMaxAgeHandler,
317 self.CacheMustRevalidateHandler,
318 self.CacheMustRevalidateMaxAgeHandler,
319 self.CacheNoStoreHandler,
320 self.CacheNoStoreMaxAgeHandler,
321 self.CacheNoTransformHandler,
322 self.DownloadHandler,
323 self.DownloadFinishHandler,
324 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000325 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000326 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000327 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000328 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000329 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000330 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000331 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000332 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.AuthBasicHandler,
334 self.AuthDigestHandler,
335 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000336 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000337 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000338 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700339 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000341 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000342 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000343 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700344 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700345 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000346 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000347 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000348 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000349 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000350 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000351 self.PostOnlyFileHandler,
352 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000353 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000354 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000355 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000356 head_handlers = [
357 self.FileHandler,
358 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000359
maruel@google.come250a9b2009-03-10 17:39:46 +0000360 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000361 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000362 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000363 'gif': 'image/gif',
364 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000365 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700366 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000367 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000368 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000369 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000370 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000371 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000372 }
initial.commit94958cf2008-07-26 22:42:52 +0000373 self._default_mime_type = 'text/html'
374
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000375 testserver_base.BasePageHandler.__init__(self, request, client_address,
376 socket_server, connect_handlers,
377 get_handlers, head_handlers,
378 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000379
initial.commit94958cf2008-07-26 22:42:52 +0000380 def GetMIMETypeFromName(self, file_name):
381 """Returns the mime type for the specified file_name. So far it only looks
382 at the file extension."""
383
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000384 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000385 if len(extension) == 0:
386 # no extension.
387 return self._default_mime_type
388
ericroman@google.comc17ca532009-05-07 03:51:05 +0000389 # extension starts with a dot, so we need to remove it
390 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000391
initial.commit94958cf2008-07-26 22:42:52 +0000392 def NoCacheMaxAgeTimeHandler(self):
393 """This request handler yields a page with the title set to the current
394 system time, and no caching requested."""
395
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000396 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000397 return False
398
399 self.send_response(200)
400 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000401 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000402 self.end_headers()
403
maruel@google.come250a9b2009-03-10 17:39:46 +0000404 self.wfile.write('<html><head><title>%s</title></head></html>' %
405 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000406
407 return True
408
409 def NoCacheTimeHandler(self):
410 """This request handler yields a page with the title set to the current
411 system time, and no caching requested."""
412
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000413 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000414 return False
415
416 self.send_response(200)
417 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000418 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000419 self.end_headers()
420
maruel@google.come250a9b2009-03-10 17:39:46 +0000421 self.wfile.write('<html><head><title>%s</title></head></html>' %
422 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000423
424 return True
425
426 def CacheTimeHandler(self):
427 """This request handler yields a page with the title set to the current
428 system time, and allows caching for one minute."""
429
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000430 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000431 return False
432
433 self.send_response(200)
434 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000435 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000436 self.end_headers()
437
maruel@google.come250a9b2009-03-10 17:39:46 +0000438 self.wfile.write('<html><head><title>%s</title></head></html>' %
439 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000440
441 return True
442
443 def CacheExpiresHandler(self):
444 """This request handler yields a page with the title set to the current
445 system time, and set the page to expire on 1 Jan 2099."""
446
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000447 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000448 return False
449
450 self.send_response(200)
451 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000452 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000453 self.end_headers()
454
maruel@google.come250a9b2009-03-10 17:39:46 +0000455 self.wfile.write('<html><head><title>%s</title></head></html>' %
456 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000457
458 return True
459
460 def CacheProxyRevalidateHandler(self):
461 """This request handler yields a page with the title set to the current
462 system time, and allows caching for 60 seconds"""
463
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000464 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000465 return False
466
467 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000468 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000469 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
470 self.end_headers()
471
maruel@google.come250a9b2009-03-10 17:39:46 +0000472 self.wfile.write('<html><head><title>%s</title></head></html>' %
473 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000474
475 return True
476
477 def CachePrivateHandler(self):
478 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700479 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000480
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000481 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000482 return False
483
484 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000485 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000486 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000487 self.end_headers()
488
maruel@google.come250a9b2009-03-10 17:39:46 +0000489 self.wfile.write('<html><head><title>%s</title></head></html>' %
490 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000491
492 return True
493
494 def CachePublicHandler(self):
495 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700496 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000497
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000498 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000499 return False
500
501 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000502 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000503 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000504 self.end_headers()
505
maruel@google.come250a9b2009-03-10 17:39:46 +0000506 self.wfile.write('<html><head><title>%s</title></head></html>' %
507 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000508
509 return True
510
511 def CacheSMaxAgeHandler(self):
512 """This request handler yields a page with the title set to the current
513 system time, and does not allow for caching."""
514
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000515 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000516 return False
517
518 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000519 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000520 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
521 self.end_headers()
522
maruel@google.come250a9b2009-03-10 17:39:46 +0000523 self.wfile.write('<html><head><title>%s</title></head></html>' %
524 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000525
526 return True
527
528 def CacheMustRevalidateHandler(self):
529 """This request handler yields a page with the title set to the current
530 system time, and does not allow caching."""
531
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000532 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000533 return False
534
535 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000536 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000537 self.send_header('Cache-Control', 'must-revalidate')
538 self.end_headers()
539
maruel@google.come250a9b2009-03-10 17:39:46 +0000540 self.wfile.write('<html><head><title>%s</title></head></html>' %
541 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000542
543 return True
544
545 def CacheMustRevalidateMaxAgeHandler(self):
546 """This request handler yields a page with the title set to the current
547 system time, and does not allow caching event though max-age of 60
548 seconds is specified."""
549
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000550 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000551 return False
552
553 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000554 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000555 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
556 self.end_headers()
557
maruel@google.come250a9b2009-03-10 17:39:46 +0000558 self.wfile.write('<html><head><title>%s</title></head></html>' %
559 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000560
561 return True
562
initial.commit94958cf2008-07-26 22:42:52 +0000563 def CacheNoStoreHandler(self):
564 """This request handler yields a page with the title set to the current
565 system time, and does not allow the page to be stored."""
566
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000567 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000568 return False
569
570 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000571 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000572 self.send_header('Cache-Control', 'no-store')
573 self.end_headers()
574
maruel@google.come250a9b2009-03-10 17:39:46 +0000575 self.wfile.write('<html><head><title>%s</title></head></html>' %
576 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000577
578 return True
579
580 def CacheNoStoreMaxAgeHandler(self):
581 """This request handler yields a page with the title set to the current
582 system time, and does not allow the page to be stored even though max-age
583 of 60 seconds is specified."""
584
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000585 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000586 return False
587
588 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000589 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000590 self.send_header('Cache-Control', 'max-age=60, no-store')
591 self.end_headers()
592
maruel@google.come250a9b2009-03-10 17:39:46 +0000593 self.wfile.write('<html><head><title>%s</title></head></html>' %
594 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000595
596 return True
597
598
599 def CacheNoTransformHandler(self):
600 """This request handler yields a page with the title set to the current
601 system time, and does not allow the content to transformed during
602 user-agent caching"""
603
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000604 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000605 return False
606
607 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000608 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000609 self.send_header('Cache-Control', 'no-transform')
610 self.end_headers()
611
maruel@google.come250a9b2009-03-10 17:39:46 +0000612 self.wfile.write('<html><head><title>%s</title></head></html>' %
613 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000614
615 return True
616
617 def EchoHeader(self):
618 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000619
ananta@chromium.org219b2062009-10-23 16:09:41 +0000620 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000621
ananta@chromium.org56812d02011-04-07 17:52:05 +0000622 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000623 """This function echoes back the value of a specific request header while
Eric Romanf6a38402018-02-14 20:19:53 +0000624 allowing caching for 10 hours."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000625
ananta@chromium.org56812d02011-04-07 17:52:05 +0000626 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000627
628 def EchoHeaderHelper(self, echo_header):
629 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000630
ananta@chromium.org219b2062009-10-23 16:09:41 +0000631 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000632 return False
633
634 query_char = self.path.find('?')
635 if query_char != -1:
636 header_name = self.path[query_char+1:]
637
638 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000639 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000640 if echo_header == '/echoheadercache':
641 self.send_header('Cache-control', 'max-age=60000')
642 else:
643 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000644 # insert a vary header to properly indicate that the cachability of this
645 # request is subject to value of the request header being echoed.
646 if len(header_name) > 0:
647 self.send_header('Vary', header_name)
648 self.end_headers()
649
650 if len(header_name) > 0:
651 self.wfile.write(self.headers.getheader(header_name))
652
653 return True
654
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000655 def ReadRequestBody(self):
656 """This function reads the body of the current HTTP request, handling
657 both plain and chunked transfer encoded requests."""
658
659 if self.headers.getheader('transfer-encoding') != 'chunked':
660 length = int(self.headers.getheader('content-length'))
661 return self.rfile.read(length)
662
663 # Read the request body as chunks.
664 body = ""
665 while True:
666 line = self.rfile.readline()
667 length = int(line, 16)
668 if length == 0:
669 self.rfile.readline()
670 break
671 body += self.rfile.read(length)
672 self.rfile.read(2)
673 return body
674
initial.commit94958cf2008-07-26 22:42:52 +0000675 def EchoHandler(self):
676 """This handler just echoes back the payload of the request, for testing
677 form submission."""
678
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000679 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000680 return False
681
hirono2838c572015-01-21 12:18:11 -0800682 _, _, _, _, query, _ = urlparse.urlparse(self.path)
683 query_params = cgi.parse_qs(query, True)
684 if 'status' in query_params:
685 self.send_response(int(query_params['status'][0]))
686 else:
687 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000688 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000689 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000690 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000691 return True
692
693 def EchoTitleHandler(self):
694 """This handler is like Echo, but sets the page title to the request."""
695
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000696 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000697 return False
698
699 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000700 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000701 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000702 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000703 self.wfile.write('<html><head><title>')
704 self.wfile.write(request)
705 self.wfile.write('</title></head></html>')
706 return True
707
708 def EchoAllHandler(self):
709 """This handler yields a (more) human-readable page listing information
710 about the request header & contents."""
711
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000712 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000713 return False
714
715 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000716 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000717 self.end_headers()
718 self.wfile.write('<html><head><style>'
719 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
720 '</style></head><body>'
721 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000722 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000723 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000724
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000725 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000726 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000727 params = cgi.parse_qs(qs, keep_blank_values=1)
728
729 for param in params:
730 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000731
732 self.wfile.write('</pre>')
733
734 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
735
736 self.wfile.write('</body></html>')
737 return True
738
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000739 def EchoMultipartPostHandler(self):
740 """This handler echoes received multipart post data as json format."""
741
742 if not (self._ShouldHandleRequest("/echomultipartpost") or
743 self._ShouldHandleRequest("/searchbyimage")):
744 return False
745
746 content_type, parameters = cgi.parse_header(
747 self.headers.getheader('content-type'))
748 if content_type == 'multipart/form-data':
749 post_multipart = cgi.parse_multipart(self.rfile, parameters)
750 elif content_type == 'application/x-www-form-urlencoded':
751 raise Exception('POST by application/x-www-form-urlencoded is '
752 'not implemented.')
753 else:
754 post_multipart = {}
755
756 # Since the data can be binary, we encode them by base64.
757 post_multipart_base64_encoded = {}
758 for field, values in post_multipart.items():
759 post_multipart_base64_encoded[field] = [base64.b64encode(value)
760 for value in values]
761
762 result = {'POST_multipart' : post_multipart_base64_encoded}
763
764 self.send_response(200)
765 self.send_header("Content-type", "text/plain")
766 self.end_headers()
767 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
768 return True
769
initial.commit94958cf2008-07-26 22:42:52 +0000770 def DownloadHandler(self):
771 """This handler sends a downloadable file with or without reporting
772 the size (6K)."""
773
774 if self.path.startswith("/download-unknown-size"):
775 send_length = False
776 elif self.path.startswith("/download-known-size"):
777 send_length = True
778 else:
779 return False
780
781 #
782 # The test which uses this functionality is attempting to send
783 # small chunks of data to the client. Use a fairly large buffer
784 # so that we'll fill chrome's IO buffer enough to force it to
785 # actually write the data.
786 # See also the comments in the client-side of this test in
787 # download_uitest.cc
788 #
789 size_chunk1 = 35*1024
790 size_chunk2 = 10*1024
791
792 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000793 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000794 self.send_header('Cache-Control', 'max-age=0')
795 if send_length:
796 self.send_header('Content-Length', size_chunk1 + size_chunk2)
797 self.end_headers()
798
799 # First chunk of data:
800 self.wfile.write("*" * size_chunk1)
801 self.wfile.flush()
802
803 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000804 self.server.wait_for_download = True
805 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000806 self.server.handle_request()
807
808 # Second chunk of data:
809 self.wfile.write("*" * size_chunk2)
810 return True
811
812 def DownloadFinishHandler(self):
813 """This handler just tells the server to finish the current download."""
814
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000815 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000816 return False
817
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000818 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000819 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000820 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000821 self.send_header('Cache-Control', 'max-age=0')
822 self.end_headers()
823 return True
824
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000825 def _ReplaceFileData(self, data, query_parameters):
826 """Replaces matching substrings in a file.
827
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000828 If the 'replace_text' URL query parameter is present, it is expected to be
829 of the form old_text:new_text, which indicates that any old_text strings in
830 the file are replaced with new_text. Multiple 'replace_text' parameters may
831 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000832
833 If the parameters are not present, |data| is returned.
834 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000835
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000836 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000837 replace_text_values = query_dict.get('replace_text', [])
838 for replace_text_value in replace_text_values:
839 replace_text_args = replace_text_value.split(':')
840 if len(replace_text_args) != 2:
841 raise ValueError(
842 'replace_text must be of form old_text:new_text. Actual value: %s' %
843 replace_text_value)
844 old_text_b64, new_text_b64 = replace_text_args
845 old_text = base64.urlsafe_b64decode(old_text_b64)
846 new_text = base64.urlsafe_b64decode(new_text_b64)
847 data = data.replace(old_text, new_text)
848 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000849
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000850 def ZipFileHandler(self):
851 """This handler sends the contents of the requested file in compressed form.
852 Can pass in a parameter that specifies that the content length be
853 C - the compressed size (OK),
854 U - the uncompressed size (Non-standard, but handled),
855 S - less than compressed (OK because we keep going),
856 M - larger than compressed but less than uncompressed (an error),
857 L - larger than uncompressed (an error)
858 Example: compressedfiles/Picture_1.doc?C
859 """
860
861 prefix = "/compressedfiles/"
862 if not self.path.startswith(prefix):
863 return False
864
865 # Consume a request body if present.
866 if self.command == 'POST' or self.command == 'PUT' :
867 self.ReadRequestBody()
868
869 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
870
871 if not query in ('C', 'U', 'S', 'M', 'L'):
872 return False
873
874 sub_path = url_path[len(prefix):]
875 entries = sub_path.split('/')
876 file_path = os.path.join(self.server.data_dir, *entries)
877 if os.path.isdir(file_path):
878 file_path = os.path.join(file_path, 'index.html')
879
880 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000881 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000882 self.send_error(404)
883 return True
884
885 f = open(file_path, "rb")
886 data = f.read()
887 uncompressed_len = len(data)
888 f.close()
889
890 # Compress the data.
891 data = zlib.compress(data)
892 compressed_len = len(data)
893
894 content_length = compressed_len
895 if query == 'U':
896 content_length = uncompressed_len
897 elif query == 'S':
898 content_length = compressed_len / 2
899 elif query == 'M':
900 content_length = (compressed_len + uncompressed_len) / 2
901 elif query == 'L':
902 content_length = compressed_len + uncompressed_len
903
904 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000905 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000906 self.send_header('Content-encoding', 'deflate')
907 self.send_header('Connection', 'close')
908 self.send_header('Content-Length', content_length)
909 self.send_header('ETag', '\'' + file_path + '\'')
910 self.end_headers()
911
912 self.wfile.write(data)
913
914 return True
915
initial.commit94958cf2008-07-26 22:42:52 +0000916 def FileHandler(self):
917 """This handler sends the contents of the requested file. Wow, it's like
918 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000919
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000920 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000921 if not self.path.startswith(prefix):
922 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000923 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000924
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000925 def PostOnlyFileHandler(self):
926 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000927
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000928 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000929 if not self.path.startswith(prefix):
930 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000931 return self._FileHandlerHelper(prefix)
932
933 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000934 request_body = ''
935 if self.command == 'POST' or self.command == 'PUT':
936 # Consume a request body if present.
937 request_body = self.ReadRequestBody()
938
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000939 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000940 query_dict = cgi.parse_qs(query)
941
942 expected_body = query_dict.get('expected_body', [])
943 if expected_body and request_body not in expected_body:
944 self.send_response(404)
945 self.end_headers()
946 self.wfile.write('')
947 return True
948
949 expected_headers = query_dict.get('expected_headers', [])
950 for expected_header in expected_headers:
951 header_name, expected_value = expected_header.split(':')
952 if self.headers.getheader(header_name) != expected_value:
953 self.send_response(404)
954 self.end_headers()
955 self.wfile.write('')
956 return True
957
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000958 sub_path = url_path[len(prefix):]
959 entries = sub_path.split('/')
960 file_path = os.path.join(self.server.data_dir, *entries)
961 if os.path.isdir(file_path):
962 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000963
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000964 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000965 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000966 self.send_error(404)
967 return True
968
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000969 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000970 data = f.read()
971 f.close()
972
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000973 data = self._ReplaceFileData(data, query)
974
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000975 old_protocol_version = self.protocol_version
976
initial.commit94958cf2008-07-26 22:42:52 +0000977 # If file.mock-http-headers exists, it contains the headers we
978 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000979 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000980 if os.path.isfile(headers_path):
981 f = open(headers_path, "r")
982
983 # "HTTP/1.1 200 OK"
984 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000985 http_major, http_minor, status_code = re.findall(
986 'HTTP/(\d+).(\d+) (\d+)', response)[0]
987 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000988 self.send_response(int(status_code))
989
990 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000991 header_values = re.findall('(\S+):\s*(.*)', line)
992 if len(header_values) > 0:
993 # "name: value"
994 name, value = header_values[0]
995 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000996 f.close()
997 else:
998 # Could be more generic once we support mime-type sniffing, but for
999 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001000
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001001 range_header = self.headers.get('Range')
1002 if range_header and range_header.startswith('bytes='):
1003 # Note this doesn't handle all valid byte range_header values (i.e.
1004 # left open ended ones), just enough for what we needed so far.
1005 range_header = range_header[6:].split('-')
1006 start = int(range_header[0])
1007 if range_header[1]:
1008 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001009 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001010 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001011
1012 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001013 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1014 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001015 self.send_header('Content-Range', content_range)
1016 data = data[start: end + 1]
1017 else:
1018 self.send_response(200)
1019
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001020 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001021 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001022 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001023 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001024 self.end_headers()
1025
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001026 if (self.command != 'HEAD'):
1027 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001028
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001029 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001030 return True
1031
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001032 def SetCookieHandler(self):
1033 """This handler just sets a cookie, for testing cookie handling."""
1034
1035 if not self._ShouldHandleRequest("/set-cookie"):
1036 return False
1037
1038 query_char = self.path.find('?')
1039 if query_char != -1:
1040 cookie_values = self.path[query_char + 1:].split('&')
1041 else:
1042 cookie_values = ("",)
1043 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001044 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001045 for cookie_value in cookie_values:
1046 self.send_header('Set-Cookie', '%s' % cookie_value)
1047 self.end_headers()
1048 for cookie_value in cookie_values:
1049 self.wfile.write('%s' % cookie_value)
1050 return True
1051
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001052 def SetManyCookiesHandler(self):
1053 """This handler just sets a given number of cookies, for testing handling
1054 of large numbers of cookies."""
1055
1056 if not self._ShouldHandleRequest("/set-many-cookies"):
1057 return False
1058
1059 query_char = self.path.find('?')
1060 if query_char != -1:
1061 num_cookies = int(self.path[query_char + 1:])
1062 else:
1063 num_cookies = 0
1064 self.send_response(200)
1065 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001066 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001067 self.send_header('Set-Cookie', 'a=')
1068 self.end_headers()
1069 self.wfile.write('%d cookies were sent' % num_cookies)
1070 return True
1071
mattm@chromium.org983fc462012-06-30 00:52:08 +00001072 def ExpectAndSetCookieHandler(self):
1073 """Expects some cookies to be sent, and if they are, sets more cookies.
1074
1075 The expect parameter specifies a required cookie. May be specified multiple
1076 times.
1077 The set parameter specifies a cookie to set if all required cookies are
1078 preset. May be specified multiple times.
1079 The data parameter specifies the response body data to be returned."""
1080
1081 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1082 return False
1083
1084 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1085 query_dict = cgi.parse_qs(query)
1086 cookies = set()
1087 if 'Cookie' in self.headers:
1088 cookie_header = self.headers.getheader('Cookie')
1089 cookies.update([s.strip() for s in cookie_header.split(';')])
1090 got_all_expected_cookies = True
1091 for expected_cookie in query_dict.get('expect', []):
1092 if expected_cookie not in cookies:
1093 got_all_expected_cookies = False
1094 self.send_response(200)
1095 self.send_header('Content-Type', 'text/html')
1096 if got_all_expected_cookies:
1097 for cookie_value in query_dict.get('set', []):
1098 self.send_header('Set-Cookie', '%s' % cookie_value)
1099 self.end_headers()
1100 for data_value in query_dict.get('data', []):
1101 self.wfile.write(data_value)
1102 return True
1103
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001104 def SetHeaderHandler(self):
1105 """This handler sets a response header. Parameters are in the
1106 key%3A%20value&key2%3A%20value2 format."""
1107
1108 if not self._ShouldHandleRequest("/set-header"):
1109 return False
1110
1111 query_char = self.path.find('?')
1112 if query_char != -1:
1113 headers_values = self.path[query_char + 1:].split('&')
1114 else:
1115 headers_values = ("",)
1116 self.send_response(200)
1117 self.send_header('Content-Type', 'text/html')
1118 for header_value in headers_values:
1119 header_value = urllib.unquote(header_value)
1120 (key, value) = header_value.split(': ', 1)
1121 self.send_header(key, value)
1122 self.end_headers()
1123 for header_value in headers_values:
1124 self.wfile.write('%s' % header_value)
1125 return True
1126
initial.commit94958cf2008-07-26 22:42:52 +00001127 def AuthBasicHandler(self):
1128 """This handler tests 'Basic' authentication. It just sends a page with
1129 title 'user/pass' if you succeed."""
1130
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001131 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001132 return False
1133
1134 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001135 expected_password = 'secret'
1136 realm = 'testrealm'
1137 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001138
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001139 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1140 query_params = cgi.parse_qs(query, True)
1141 if 'set-cookie-if-challenged' in query_params:
1142 set_cookie_if_challenged = True
1143 if 'password' in query_params:
1144 expected_password = query_params['password'][0]
1145 if 'realm' in query_params:
1146 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001147
initial.commit94958cf2008-07-26 22:42:52 +00001148 auth = self.headers.getheader('authorization')
1149 try:
1150 if not auth:
1151 raise Exception('no auth')
1152 b64str = re.findall(r'Basic (\S+)', auth)[0]
1153 userpass = base64.b64decode(b64str)
1154 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001155 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001156 raise Exception('wrong password')
1157 except Exception, e:
1158 # Authentication failed.
1159 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001160 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001161 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001162 if set_cookie_if_challenged:
1163 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001164 self.end_headers()
1165 self.wfile.write('<html><head>')
1166 self.wfile.write('<title>Denied: %s</title>' % e)
1167 self.wfile.write('</head><body>')
1168 self.wfile.write('auth=%s<p>' % auth)
1169 self.wfile.write('b64str=%s<p>' % b64str)
1170 self.wfile.write('username: %s<p>' % username)
1171 self.wfile.write('userpass: %s<p>' % userpass)
1172 self.wfile.write('password: %s<p>' % password)
1173 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1174 self.wfile.write('</body></html>')
1175 return True
1176
1177 # Authentication successful. (Return a cachable response to allow for
1178 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001179 old_protocol_version = self.protocol_version
1180 self.protocol_version = "HTTP/1.1"
1181
initial.commit94958cf2008-07-26 22:42:52 +00001182 if_none_match = self.headers.getheader('if-none-match')
1183 if if_none_match == "abc":
1184 self.send_response(304)
1185 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001186 elif url_path.endswith(".gif"):
1187 # Using chrome/test/data/google/logo.gif as the test image
1188 test_image_path = ['google', 'logo.gif']
1189 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1190 if not os.path.isfile(gif_path):
1191 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001192 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001193 return True
1194
1195 f = open(gif_path, "rb")
1196 data = f.read()
1197 f.close()
1198
1199 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001200 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001201 self.send_header('Cache-control', 'max-age=60000')
1202 self.send_header('Etag', 'abc')
1203 self.end_headers()
1204 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001205 else:
1206 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001207 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001208 self.send_header('Cache-control', 'max-age=60000')
1209 self.send_header('Etag', 'abc')
1210 self.end_headers()
1211 self.wfile.write('<html><head>')
1212 self.wfile.write('<title>%s/%s</title>' % (username, password))
1213 self.wfile.write('</head><body>')
1214 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001215 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001216 self.wfile.write('</body></html>')
1217
rvargas@google.com54453b72011-05-19 01:11:11 +00001218 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001219 return True
1220
tonyg@chromium.org75054202010-03-31 22:06:10 +00001221 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001222 """Returns a nonce that's stable per request path for the server's lifetime.
1223 This is a fake implementation. A real implementation would only use a given
1224 nonce a single time (hence the name n-once). However, for the purposes of
1225 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001226
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001227 Args:
1228 force_reset: Iff set, the nonce will be changed. Useful for testing the
1229 "stale" response.
1230 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001231
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001232 if force_reset or not self.server.nonce_time:
1233 self.server.nonce_time = time.time()
1234 return hashlib.md5('privatekey%s%d' %
1235 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001236
1237 def AuthDigestHandler(self):
1238 """This handler tests 'Digest' authentication.
1239
1240 It just sends a page with title 'user/pass' if you succeed.
1241
1242 A stale response is sent iff "stale" is present in the request path.
1243 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001244
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001245 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001246 return False
1247
tonyg@chromium.org75054202010-03-31 22:06:10 +00001248 stale = 'stale' in self.path
1249 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001250 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001251 password = 'secret'
1252 realm = 'testrealm'
1253
1254 auth = self.headers.getheader('authorization')
1255 pairs = {}
1256 try:
1257 if not auth:
1258 raise Exception('no auth')
1259 if not auth.startswith('Digest'):
1260 raise Exception('not digest')
1261 # Pull out all the name="value" pairs as a dictionary.
1262 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1263
1264 # Make sure it's all valid.
1265 if pairs['nonce'] != nonce:
1266 raise Exception('wrong nonce')
1267 if pairs['opaque'] != opaque:
1268 raise Exception('wrong opaque')
1269
1270 # Check the 'response' value and make sure it matches our magic hash.
1271 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001272 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001273 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001274 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001275 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001276 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001277 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1278 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001279 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001280
1281 if pairs['response'] != response:
1282 raise Exception('wrong password')
1283 except Exception, e:
1284 # Authentication failed.
1285 self.send_response(401)
1286 hdr = ('Digest '
1287 'realm="%s", '
1288 'domain="/", '
1289 'qop="auth", '
1290 'algorithm=MD5, '
1291 'nonce="%s", '
1292 'opaque="%s"') % (realm, nonce, opaque)
1293 if stale:
1294 hdr += ', stale="TRUE"'
1295 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001296 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001297 self.end_headers()
1298 self.wfile.write('<html><head>')
1299 self.wfile.write('<title>Denied: %s</title>' % e)
1300 self.wfile.write('</head><body>')
1301 self.wfile.write('auth=%s<p>' % auth)
1302 self.wfile.write('pairs=%s<p>' % pairs)
1303 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1304 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1305 self.wfile.write('</body></html>')
1306 return True
1307
1308 # Authentication successful.
1309 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001310 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001311 self.end_headers()
1312 self.wfile.write('<html><head>')
1313 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1314 self.wfile.write('</head><body>')
1315 self.wfile.write('auth=%s<p>' % auth)
1316 self.wfile.write('pairs=%s<p>' % pairs)
1317 self.wfile.write('</body></html>')
1318
1319 return True
1320
1321 def SlowServerHandler(self):
1322 """Wait for the user suggested time before responding. The syntax is
1323 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001324
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001325 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001326 return False
1327 query_char = self.path.find('?')
1328 wait_sec = 1.0
1329 if query_char >= 0:
1330 try:
davidben05f82202015-03-31 13:48:07 -07001331 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001332 except ValueError:
1333 pass
1334 time.sleep(wait_sec)
1335 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001336 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001337 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001338 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001339 return True
1340
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001341 def ChunkedServerHandler(self):
1342 """Send chunked response. Allows to specify chunks parameters:
1343 - waitBeforeHeaders - ms to wait before sending headers
1344 - waitBetweenChunks - ms to wait between chunks
1345 - chunkSize - size of each chunk in bytes
1346 - chunksNumber - number of chunks
1347 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1348 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001349
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001350 if not self._ShouldHandleRequest("/chunked"):
1351 return False
1352 query_char = self.path.find('?')
1353 chunkedSettings = {'waitBeforeHeaders' : 0,
1354 'waitBetweenChunks' : 0,
1355 'chunkSize' : 5,
1356 'chunksNumber' : 5}
1357 if query_char >= 0:
1358 params = self.path[query_char + 1:].split('&')
1359 for param in params:
1360 keyValue = param.split('=')
1361 if len(keyValue) == 2:
1362 try:
1363 chunkedSettings[keyValue[0]] = int(keyValue[1])
1364 except ValueError:
1365 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001366 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001367 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1368 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001369 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001370 self.send_header('Connection', 'close')
1371 self.send_header('Transfer-Encoding', 'chunked')
1372 self.end_headers()
1373 # Chunked encoding: sending all chunks, then final zero-length chunk and
1374 # then final CRLF.
1375 for i in range(0, chunkedSettings['chunksNumber']):
1376 if i > 0:
1377 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1378 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001379 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001380 self.sendChunkHelp('')
1381 return True
1382
creis@google.com2f4f6a42011-03-25 19:44:19 +00001383 def NoContentHandler(self):
1384 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001385
creis@google.com2f4f6a42011-03-25 19:44:19 +00001386 if not self._ShouldHandleRequest("/nocontent"):
1387 return False
1388 self.send_response(204)
1389 self.end_headers()
1390 return True
1391
initial.commit94958cf2008-07-26 22:42:52 +00001392 def ServerRedirectHandler(self):
1393 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001394 '/server-redirect?http://foo.bar/asdf' to redirect to
1395 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001396
1397 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001398 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001399 return False
1400
1401 query_char = self.path.find('?')
1402 if query_char < 0 or len(self.path) <= query_char + 1:
1403 self.sendRedirectHelp(test_name)
1404 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001405 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001406
1407 self.send_response(301) # moved permanently
1408 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001409 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001410 self.end_headers()
1411 self.wfile.write('<html><head>')
1412 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1413
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001414 return True
initial.commit94958cf2008-07-26 22:42:52 +00001415
naskoe7a0d0d2014-09-29 08:53:05 -07001416 def CrossSiteRedirectHandler(self):
1417 """Sends a server redirect to the given site. The syntax is
1418 '/cross-site/hostname/...' to redirect to //hostname/...
1419 It is used to navigate between different Sites, causing
1420 cross-site/cross-process navigations in the browser."""
1421
1422 test_name = "/cross-site"
1423 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001424 return False
1425
1426 params = urllib.unquote(self.path[(len(test_name) + 1):])
1427 slash = params.find('/')
1428 if slash < 0:
1429 self.sendRedirectHelp(test_name)
1430 return True
1431
1432 host = params[:slash]
1433 path = params[(slash+1):]
1434 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1435
1436 self.send_response(301) # moved permanently
1437 self.send_header('Location', dest)
1438 self.send_header('Content-Type', 'text/html')
1439 self.end_headers()
1440 self.wfile.write('<html><head>')
1441 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1442
1443 return True
1444
initial.commit94958cf2008-07-26 22:42:52 +00001445 def ClientRedirectHandler(self):
1446 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001447 '/client-redirect?http://foo.bar/asdf' to redirect to
1448 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001449
1450 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001451 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001452 return False
1453
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001454 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001455 if query_char < 0 or len(self.path) <= query_char + 1:
1456 self.sendRedirectHelp(test_name)
1457 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001458 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001459
1460 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001461 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001462 self.end_headers()
1463 self.wfile.write('<html><head>')
1464 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1465 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1466
1467 return True
1468
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001469 def GetSSLSessionCacheHandler(self):
1470 """Send a reply containing a log of the session cache operations."""
1471
1472 if not self._ShouldHandleRequest('/ssl-session-cache'):
1473 return False
1474
1475 self.send_response(200)
1476 self.send_header('Content-Type', 'text/plain')
1477 self.end_headers()
1478 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001479 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001480 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001481 self.wfile.write('Pass --https-record-resume in order to use' +
1482 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001483 return True
1484
1485 for (action, sessionID) in log:
1486 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001487 return True
1488
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001489 def SSLManySmallRecords(self):
1490 """Sends a reply consisting of a variety of small writes. These will be
1491 translated into a series of small SSL records when used over an HTTPS
1492 server."""
1493
1494 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1495 return False
1496
1497 self.send_response(200)
1498 self.send_header('Content-Type', 'text/plain')
1499 self.end_headers()
1500
1501 # Write ~26K of data, in 1350 byte chunks
1502 for i in xrange(20):
1503 self.wfile.write('*' * 1350)
1504 self.wfile.flush()
1505 return True
1506
agl@chromium.org04700be2013-03-02 18:40:41 +00001507 def GetChannelID(self):
1508 """Send a reply containing the hashed ChannelID that the client provided."""
1509
1510 if not self._ShouldHandleRequest('/channel-id'):
1511 return False
1512
1513 self.send_response(200)
1514 self.send_header('Content-Type', 'text/plain')
1515 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001516 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001517 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1518 return True
1519
pneubeckfd4f0442015-08-07 04:55:10 -07001520 def GetClientCert(self):
1521 """Send a reply whether a client certificate was provided."""
1522
1523 if not self._ShouldHandleRequest('/client-cert'):
1524 return False
1525
1526 self.send_response(200)
1527 self.send_header('Content-Type', 'text/plain')
1528 self.end_headers()
1529
1530 cert_chain = self.server.tlsConnection.session.clientCertChain
1531 if cert_chain != None:
1532 self.wfile.write('got client cert with fingerprint: ' +
1533 cert_chain.getFingerprint())
1534 else:
1535 self.wfile.write('got no client cert')
1536 return True
1537
davidben599e7e72014-09-03 16:19:09 -07001538 def ClientCipherListHandler(self):
1539 """Send a reply containing the cipher suite list that the client
1540 provided. Each cipher suite value is serialized in decimal, followed by a
1541 newline."""
1542
1543 if not self._ShouldHandleRequest('/client-cipher-list'):
1544 return False
1545
1546 self.send_response(200)
1547 self.send_header('Content-Type', 'text/plain')
1548 self.end_headers()
1549
davidben11682512014-10-06 21:09:11 -07001550 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1551 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001552 return True
1553
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001554 def CloseSocketHandler(self):
1555 """Closes the socket without sending anything."""
1556
1557 if not self._ShouldHandleRequest('/close-socket'):
1558 return False
1559
1560 self.wfile.close()
1561 return True
1562
initial.commit94958cf2008-07-26 22:42:52 +00001563 def DefaultResponseHandler(self):
1564 """This is the catch-all response handler for requests that aren't handled
1565 by one of the special handlers above.
1566 Note that we specify the content-length as without it the https connection
1567 is not closed properly (and the browser keeps expecting data)."""
1568
1569 contents = "Default response given for path: " + self.path
1570 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001571 self.send_header('Content-Type', 'text/html')
1572 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001573 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001574 if (self.command != 'HEAD'):
1575 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001576 return True
1577
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001578 def RedirectConnectHandler(self):
1579 """Sends a redirect to the CONNECT request for www.redirect.com. This
1580 response is not specified by the RFC, so the browser should not follow
1581 the redirect."""
1582
1583 if (self.path.find("www.redirect.com") < 0):
1584 return False
1585
1586 dest = "http://www.destination.com/foo.js"
1587
1588 self.send_response(302) # moved temporarily
1589 self.send_header('Location', dest)
1590 self.send_header('Connection', 'close')
1591 self.end_headers()
1592 return True
1593
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001594 def ServerAuthConnectHandler(self):
1595 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1596 response doesn't make sense because the proxy server cannot request
1597 server authentication."""
1598
1599 if (self.path.find("www.server-auth.com") < 0):
1600 return False
1601
1602 challenge = 'Basic realm="WallyWorld"'
1603
1604 self.send_response(401) # unauthorized
1605 self.send_header('WWW-Authenticate', challenge)
1606 self.send_header('Connection', 'close')
1607 self.end_headers()
1608 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001609
1610 def DefaultConnectResponseHandler(self):
1611 """This is the catch-all response handler for CONNECT requests that aren't
1612 handled by one of the special handlers above. Real Web servers respond
1613 with 400 to CONNECT requests."""
1614
1615 contents = "Your client has issued a malformed or illegal request."
1616 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001617 self.send_header('Content-Type', 'text/html')
1618 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001619 self.end_headers()
1620 self.wfile.write(contents)
1621 return True
1622
initial.commit94958cf2008-07-26 22:42:52 +00001623 # called by the redirect handling function when there is no parameter
1624 def sendRedirectHelp(self, redirect_name):
1625 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001626 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001627 self.end_headers()
1628 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1629 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1630 self.wfile.write('</body></html>')
1631
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001632 # called by chunked handling function
1633 def sendChunkHelp(self, chunk):
1634 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1635 self.wfile.write('%X\r\n' % len(chunk))
1636 self.wfile.write(chunk)
1637 self.wfile.write('\r\n')
1638
akalin@chromium.org154bb132010-11-12 02:20:27 +00001639
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001640class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001641 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001642 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001643 self.ocsp_response = socket_server.ocsp_response
Matt Mueller55aef642018-05-02 18:53:57 +00001644 self.ocsp_response_intermediate = socket_server.ocsp_response_intermediate
mattm10ede842016-11-29 11:57:16 -08001645 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001646 testserver_base.BasePageHandler.__init__(self, request, client_address,
1647 socket_server, [], handlers, [],
1648 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001649
1650 def OCSPResponse(self):
Matt Mueller55aef642018-05-02 18:53:57 +00001651 if self._ShouldHandleRequest("/ocsp"):
1652 response = self.ocsp_response
1653 elif self._ShouldHandleRequest("/ocsp_intermediate"):
1654 response = self.ocsp_response_intermediate
1655 else:
mattm10ede842016-11-29 11:57:16 -08001656 return False
1657 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001658 self.send_response(200)
1659 self.send_header('Content-Type', 'application/ocsp-response')
Matt Mueller55aef642018-05-02 18:53:57 +00001660 self.send_header('Content-Length', str(len(response)))
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001661 self.end_headers()
1662
Matt Mueller55aef642018-05-02 18:53:57 +00001663 self.wfile.write(response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001664
mattm10ede842016-11-29 11:57:16 -08001665 def CaIssuersResponse(self):
1666 if not self._ShouldHandleRequest("/ca_issuers"):
1667 return False
1668 print 'handling ca_issuers request'
1669 self.send_response(200)
1670 self.send_header('Content-Type', 'application/pkix-cert')
1671 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1672 self.end_headers()
1673
1674 self.wfile.write(self.ca_issuers_response)
1675
mattm@chromium.org830a3712012-11-07 23:00:07 +00001676
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001677class TCPEchoHandler(SocketServer.BaseRequestHandler):
1678 """The RequestHandler class for TCP echo server.
1679
1680 It is instantiated once per connection to the server, and overrides the
1681 handle() method to implement communication to the client.
1682 """
1683
1684 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001685 """Handles the request from the client and constructs a response."""
1686
1687 data = self.request.recv(65536).strip()
1688 # Verify the "echo request" message received from the client. Send back
1689 # "echo response" message if "echo request" message is valid.
1690 try:
1691 return_data = echo_message.GetEchoResponseData(data)
1692 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001693 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001694 except ValueError:
1695 return
1696
1697 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001698
1699
1700class UDPEchoHandler(SocketServer.BaseRequestHandler):
1701 """The RequestHandler class for UDP echo server.
1702
1703 It is instantiated once per connection to the server, and overrides the
1704 handle() method to implement communication to the client.
1705 """
1706
1707 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001708 """Handles the request from the client and constructs a response."""
1709
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001710 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001711 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001712 # Verify the "echo request" message received from the client. Send back
1713 # "echo response" message if "echo request" message is valid.
1714 try:
1715 return_data = echo_message.GetEchoResponseData(data)
1716 if not return_data:
1717 return
1718 except ValueError:
1719 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001720 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001721
1722
Adam Rice9476b8c2018-08-02 15:28:43 +00001723class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1724 """A request handler that behaves as a proxy server. Only CONNECT, GET and
1725 HEAD methods are supported.
bashi@chromium.org33233532012-09-08 17:37:24 +00001726 """
1727
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001728 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001729
bashi@chromium.org33233532012-09-08 17:37:24 +00001730 def _start_read_write(self, sock):
1731 sock.setblocking(0)
1732 self.request.setblocking(0)
1733 rlist = [self.request, sock]
1734 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001735 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001736 if errors:
1737 self.send_response(500)
1738 self.end_headers()
1739 return
1740 for s in ready_sockets:
1741 received = s.recv(1024)
1742 if len(received) == 0:
1743 return
1744 if s == self.request:
1745 other = sock
1746 else:
1747 other = self.request
Adam Rice54443aa2018-06-06 00:11:54 +00001748 # This will lose data if the kernel write buffer fills up.
1749 # TODO(ricea): Correctly use the return value to track how much was
1750 # written and buffer the rest. Use select to determine when the socket
1751 # becomes writable again.
bashi@chromium.org33233532012-09-08 17:37:24 +00001752 other.send(received)
1753
1754 def _do_common_method(self):
1755 url = urlparse.urlparse(self.path)
1756 port = url.port
1757 if not port:
1758 if url.scheme == 'http':
1759 port = 80
1760 elif url.scheme == 'https':
1761 port = 443
1762 if not url.hostname or not port:
1763 self.send_response(400)
1764 self.end_headers()
1765 return
1766
1767 if len(url.path) == 0:
1768 path = '/'
1769 else:
1770 path = url.path
1771 if len(url.query) > 0:
1772 path = '%s?%s' % (url.path, url.query)
1773
1774 sock = None
1775 try:
1776 sock = socket.create_connection((url.hostname, port))
1777 sock.send('%s %s %s\r\n' % (
1778 self.command, path, self.protocol_version))
1779 for header in self.headers.headers:
1780 header = header.strip()
1781 if (header.lower().startswith('connection') or
1782 header.lower().startswith('proxy')):
1783 continue
1784 sock.send('%s\r\n' % header)
1785 sock.send('\r\n')
Adam Rice54443aa2018-06-06 00:11:54 +00001786 # This is wrong: it will pass through connection-level headers and
1787 # misbehave on connection reuse. The only reason it works at all is that
1788 # our test servers have never supported connection reuse.
1789 # TODO(ricea): Use a proper HTTP client library instead.
bashi@chromium.org33233532012-09-08 17:37:24 +00001790 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001791 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001792 logging.exception('failure in common method: %s %s', self.command, path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001793 self.send_response(500)
1794 self.end_headers()
1795 finally:
1796 if sock is not None:
1797 sock.close()
1798
1799 def do_CONNECT(self):
1800 try:
1801 pos = self.path.rfind(':')
1802 host = self.path[:pos]
1803 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001804 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001805 self.send_response(400)
1806 self.end_headers()
1807
Adam Rice9476b8c2018-08-02 15:28:43 +00001808 if ProxyRequestHandler.redirect_connect_to_localhost:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001809 host = "127.0.0.1"
1810
Adam Rice54443aa2018-06-06 00:11:54 +00001811 sock = None
bashi@chromium.org33233532012-09-08 17:37:24 +00001812 try:
1813 sock = socket.create_connection((host, port))
1814 self.send_response(200, 'Connection established')
1815 self.end_headers()
1816 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001817 except Exception:
Adam Rice54443aa2018-06-06 00:11:54 +00001818 logging.exception('failure in CONNECT: %s', path)
bashi@chromium.org33233532012-09-08 17:37:24 +00001819 self.send_response(500)
1820 self.end_headers()
1821 finally:
Adam Rice54443aa2018-06-06 00:11:54 +00001822 if sock is not None:
1823 sock.close()
bashi@chromium.org33233532012-09-08 17:37:24 +00001824
1825 def do_GET(self):
1826 self._do_common_method()
1827
1828 def do_HEAD(self):
1829 self._do_common_method()
1830
Adam Rice9476b8c2018-08-02 15:28:43 +00001831class BasicAuthProxyRequestHandler(ProxyRequestHandler):
1832 """A request handler that behaves as a proxy server which requires
1833 basic authentication.
1834 """
1835
1836 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1837
1838 def parse_request(self):
1839 """Overrides parse_request to check credential."""
1840
1841 if not ProxyRequestHandler.parse_request(self):
1842 return False
1843
1844 auth = self.headers.getheader('Proxy-Authorization')
1845 if auth != self._AUTH_CREDENTIAL:
1846 self.send_response(407)
1847 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1848 self.end_headers()
1849 return False
1850
1851 return True
1852
bashi@chromium.org33233532012-09-08 17:37:24 +00001853
mattm@chromium.org830a3712012-11-07 23:00:07 +00001854class ServerRunner(testserver_base.TestServerRunner):
1855 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001856
mattm@chromium.org830a3712012-11-07 23:00:07 +00001857 def __init__(self):
1858 super(ServerRunner, self).__init__()
1859 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001860
mattm@chromium.org830a3712012-11-07 23:00:07 +00001861 def __make_data_dir(self):
1862 if self.options.data_dir:
1863 if not os.path.isdir(self.options.data_dir):
1864 raise testserver_base.OptionError('specified data dir not found: ' +
1865 self.options.data_dir + ' exiting...')
1866 my_data_dir = self.options.data_dir
1867 else:
1868 # Create the default path to our data dir, relative to the exe dir.
1869 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1870 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001871
mattm@chromium.org830a3712012-11-07 23:00:07 +00001872 #TODO(ibrar): Must use Find* funtion defined in google\tools
1873 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001874
mattm@chromium.org830a3712012-11-07 23:00:07 +00001875 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001876
Matt Mueller55aef642018-05-02 18:53:57 +00001877 def __parse_ocsp_options(self, states_option, date_option, produced_option):
1878 if states_option is None:
1879 return None, None, None
1880
1881 ocsp_states = list()
1882 for ocsp_state_arg in states_option.split(':'):
1883 if ocsp_state_arg == 'ok':
1884 ocsp_state = minica.OCSP_STATE_GOOD
1885 elif ocsp_state_arg == 'revoked':
1886 ocsp_state = minica.OCSP_STATE_REVOKED
1887 elif ocsp_state_arg == 'invalid':
1888 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1889 elif ocsp_state_arg == 'unauthorized':
1890 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1891 elif ocsp_state_arg == 'unknown':
1892 ocsp_state = minica.OCSP_STATE_UNKNOWN
1893 elif ocsp_state_arg == 'later':
1894 ocsp_state = minica.OCSP_STATE_TRY_LATER
1895 elif ocsp_state_arg == 'invalid_data':
1896 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1897 elif ocsp_state_arg == "mismatched_serial":
1898 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1899 else:
1900 raise testserver_base.OptionError('unknown OCSP status: ' +
1901 ocsp_state_arg)
1902 ocsp_states.append(ocsp_state)
1903
1904 if len(ocsp_states) > 1:
1905 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1906 raise testserver_base.OptionError('Multiple OCSP responses '
1907 'incompatible with states ' + str(ocsp_states))
1908
1909 ocsp_dates = list()
1910 for ocsp_date_arg in date_option.split(':'):
1911 if ocsp_date_arg == 'valid':
1912 ocsp_date = minica.OCSP_DATE_VALID
1913 elif ocsp_date_arg == 'old':
1914 ocsp_date = minica.OCSP_DATE_OLD
1915 elif ocsp_date_arg == 'early':
1916 ocsp_date = minica.OCSP_DATE_EARLY
1917 elif ocsp_date_arg == 'long':
1918 ocsp_date = minica.OCSP_DATE_LONG
1919 elif ocsp_date_arg == 'longer':
1920 ocsp_date = minica.OCSP_DATE_LONGER
1921 else:
1922 raise testserver_base.OptionError('unknown OCSP date: ' +
1923 ocsp_date_arg)
1924 ocsp_dates.append(ocsp_date)
1925
1926 if len(ocsp_states) != len(ocsp_dates):
1927 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1928 'count')
1929
1930 ocsp_produced = None
1931 if produced_option == 'valid':
1932 ocsp_produced = minica.OCSP_PRODUCED_VALID
1933 elif produced_option == 'before':
1934 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1935 elif produced_option == 'after':
1936 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
1937 else:
1938 raise testserver_base.OptionError('unknown OCSP produced: ' +
1939 produced_option)
1940
1941 return ocsp_states, ocsp_dates, ocsp_produced
1942
mattm@chromium.org830a3712012-11-07 23:00:07 +00001943 def create_server(self, server_data):
1944 port = self.options.port
1945 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001946
Adam Rice54443aa2018-06-06 00:11:54 +00001947 logging.basicConfig()
1948
estark21667d62015-04-08 21:00:16 -07001949 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1950 # will result in a call to |getaddrinfo|, which fails with "nodename
1951 # nor servname provided" for localhost:0 on 10.6.
Adam Rice54443aa2018-06-06 00:11:54 +00001952 # TODO(ricea): Remove this if no longer needed.
estark21667d62015-04-08 21:00:16 -07001953 if self.options.server_type == SERVER_WEBSOCKET and \
1954 host == "localhost" and \
1955 port == 0:
1956 host = "127.0.0.1"
1957
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001958 # Construct the subjectAltNames for any ad-hoc generated certificates.
1959 # As host can be either a DNS name or IP address, attempt to determine
1960 # which it is, so it can be placed in the appropriate SAN.
1961 dns_sans = None
1962 ip_sans = None
1963 ip = None
1964 try:
1965 ip = socket.inet_aton(host)
1966 ip_sans = [ip]
1967 except socket.error:
1968 pass
1969 if ip is None:
1970 dns_sans = [host]
1971
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 if self.options.server_type == SERVER_HTTP:
1973 if self.options.https:
1974 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001975 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 if self.options.cert_and_key_file:
1977 if not os.path.isfile(self.options.cert_and_key_file):
1978 raise testserver_base.OptionError(
1979 'specified server cert file not found: ' +
1980 self.options.cert_and_key_file + ' exiting...')
1981 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001982 elif self.options.aia_intermediate:
1983 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1984 print ('AIA server started on %s:%d...' %
1985 (host, self.__ocsp_server.server_port))
1986
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001987 ocsp_server_port = self.__ocsp_server.server_port
1988 if self.options.ocsp_proxy_port_number != 0:
1989 ocsp_server_port = self.options.ocsp_proxy_port_number
1990 server_data['ocsp_port'] = self.__ocsp_server.server_port
1991
mattm10ede842016-11-29 11:57:16 -08001992 (pem_cert_and_key, intermediate_cert_der) = \
1993 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001994 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00001995 ip_sans=ip_sans, dns_sans=dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001996 ca_issuers_url =
1997 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08001998 serial = self.options.cert_serial)
1999
2000 self.__ocsp_server.ocsp_response = None
Matt Mueller55aef642018-05-02 18:53:57 +00002001 self.__ocsp_server.ocsp_response_intermediate = None
mattm10ede842016-11-29 11:57:16 -08002002 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002003 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004 # generate a new certificate and run an OCSP server for it.
2005 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002006 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00002007 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002008
Matt Mueller55aef642018-05-02 18:53:57 +00002009 ocsp_states, ocsp_dates, ocsp_produced = self.__parse_ocsp_options(
2010 self.options.ocsp,
2011 self.options.ocsp_date,
2012 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002013
Matt Mueller55aef642018-05-02 18:53:57 +00002014 (ocsp_intermediate_states, ocsp_intermediate_dates,
2015 ocsp_intermediate_produced) = self.__parse_ocsp_options(
2016 self.options.ocsp_intermediate,
2017 self.options.ocsp_intermediate_date,
2018 self.options.ocsp_intermediate_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002019
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002020 ocsp_server_port = self.__ocsp_server.server_port
2021 if self.options.ocsp_proxy_port_number != 0:
2022 ocsp_server_port = self.options.ocsp_proxy_port_number
2023 server_data['ocsp_port'] = self.__ocsp_server.server_port
2024
Matt Mueller55aef642018-05-02 18:53:57 +00002025 pem_cert_and_key, (ocsp_der,
2026 ocsp_intermediate_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00002027 subject = self.options.cert_common_name,
Ryan Sleevi3bfd15d2018-01-23 21:12:24 +00002028 ip_sans = ip_sans,
2029 dns_sans = dns_sans,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002030 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07002031 ocsp_states = ocsp_states,
2032 ocsp_dates = ocsp_dates,
2033 ocsp_produced = ocsp_produced,
Matt Mueller55aef642018-05-02 18:53:57 +00002034 ocsp_intermediate_url = (
2035 "http://%s:%d/ocsp_intermediate" % (host, ocsp_server_port)
2036 if ocsp_intermediate_states else None),
2037 ocsp_intermediate_states = ocsp_intermediate_states,
2038 ocsp_intermediate_dates = ocsp_intermediate_dates,
2039 ocsp_intermediate_produced = ocsp_intermediate_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002040 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002041
davidben3e2564a2014-11-07 18:51:00 -08002042 if self.options.ocsp_server_unavailable:
2043 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
Matt Mueller55aef642018-05-02 18:53:57 +00002044 self.__ocsp_server.ocsp_response_intermediate = \
2045 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
davidben3e2564a2014-11-07 18:51:00 -08002046 else:
2047 self.__ocsp_server.ocsp_response = ocsp_der
Matt Mueller55aef642018-05-02 18:53:57 +00002048 self.__ocsp_server.ocsp_response_intermediate = \
2049 ocsp_intermediate_der
mattm10ede842016-11-29 11:57:16 -08002050 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002051
2052 for ca_cert in self.options.ssl_client_ca:
2053 if not os.path.isfile(ca_cert):
2054 raise testserver_base.OptionError(
2055 'specified trusted client CA file not found: ' + ca_cert +
2056 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002057
2058 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002059 if self.options.staple_ocsp_response:
Matt Mueller55aef642018-05-02 18:53:57 +00002060 # TODO(mattm): Staple the intermediate response too (if applicable,
2061 # and if chrome ever supports it).
davidben3e2564a2014-11-07 18:51:00 -08002062 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002063
mattm@chromium.org830a3712012-11-07 23:00:07 +00002064 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2065 self.options.ssl_client_auth,
2066 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002067 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002069 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002070 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002071 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002072 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002073 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002074 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002075 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002076 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002077 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002078 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002079 self.options.alert_after_handshake,
2080 self.options.disable_channel_id,
Nick Harper4a22b722018-09-28 20:33:26 +00002081 self.options.disable_extended_master_secret)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002082 print 'HTTPS server started on https://%s:%d...' % \
2083 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002084 else:
2085 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002086 print 'HTTP server started on http://%s:%d...' % \
2087 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088
2089 server.data_dir = self.__make_data_dir()
2090 server.file_root_url = self.options.file_root_url
2091 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002092 elif self.options.server_type == SERVER_WEBSOCKET:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002093 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2094 # is required to work correctly. It should be fixed from pywebsocket side.
2095 os.chdir(self.__make_data_dir())
2096 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002097 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002098 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002099 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002100 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002101 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2102 if not os.path.isfile(key_path):
2103 raise testserver_base.OptionError(
2104 'specified server cert file not found: ' +
2105 self.options.cert_and_key_file + ' exiting...')
2106 websocket_options.private_key = key_path
2107 websocket_options.certificate = key_path
2108
mattm@chromium.org830a3712012-11-07 23:00:07 +00002109 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002110 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002111 websocket_options.tls_client_auth = True
2112 if len(self.options.ssl_client_ca) != 1:
2113 raise testserver_base.OptionError(
2114 'one trusted client CA file should be specified')
2115 if not os.path.isfile(self.options.ssl_client_ca[0]):
2116 raise testserver_base.OptionError(
2117 'specified trusted client CA file not found: ' +
2118 self.options.ssl_client_ca[0] + ' exiting...')
2119 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002120 print 'Trying to start websocket server on %s://%s:%d...' % \
2121 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002122 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002123 print 'WebSocket server started on %s://%s:%d...' % \
2124 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002126 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002127 elif self.options.server_type == SERVER_TCP_ECHO:
2128 # Used for generating the key (randomly) that encodes the "echo request"
2129 # message.
2130 random.seed()
2131 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002132 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002133 server_data['port'] = server.server_port
2134 elif self.options.server_type == SERVER_UDP_ECHO:
2135 # Used for generating the key (randomly) that encodes the "echo request"
2136 # message.
2137 random.seed()
2138 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002139 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002140 server_data['port'] = server.server_port
Adam Rice9476b8c2018-08-02 15:28:43 +00002141 elif self.options.server_type == SERVER_PROXY:
2142 ProxyRequestHandler.redirect_connect_to_localhost = \
2143 self.options.redirect_connect_to_localhost
2144 server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
2145 print 'Proxy server started on port %d...' % server.server_port
2146 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002147 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Adam Rice9476b8c2018-08-02 15:28:43 +00002148 ProxyRequestHandler.redirect_connect_to_localhost = \
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002149 self.options.redirect_connect_to_localhost
Adam Rice34b2e312018-04-06 16:48:30 +00002150 server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002151 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002152 server_data['port'] = server.server_port
2153 elif self.options.server_type == SERVER_FTP:
2154 my_data_dir = self.__make_data_dir()
2155
2156 # Instantiate a dummy authorizer for managing 'virtual' users
2157 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2158
xleng9d4c45f2015-05-04 16:26:12 -07002159 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002160 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2161
xleng9d4c45f2015-05-04 16:26:12 -07002162 # Define a read-only anonymous user unless disabled
2163 if not self.options.no_anonymous_ftp_user:
2164 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002165
2166 # Instantiate FTP handler class
2167 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2168 ftp_handler.authorizer = authorizer
2169
2170 # Define a customized banner (string returned when client connects)
2171 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2172 pyftpdlib.ftpserver.__ver__)
2173
2174 # Instantiate FTP server class and listen to address:port
2175 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2176 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002177 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002178 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002179 raise testserver_base.OptionError('unknown server type' +
2180 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002181
mattm@chromium.org830a3712012-11-07 23:00:07 +00002182 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002183
mattm@chromium.org830a3712012-11-07 23:00:07 +00002184 def run_server(self):
2185 if self.__ocsp_server:
2186 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002187
mattm@chromium.org830a3712012-11-07 23:00:07 +00002188 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002189
mattm@chromium.org830a3712012-11-07 23:00:07 +00002190 if self.__ocsp_server:
2191 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002192
mattm@chromium.org830a3712012-11-07 23:00:07 +00002193 def add_options(self):
2194 testserver_base.TestServerRunner.add_options(self)
2195 self.option_parser.add_option('-f', '--ftp', action='store_const',
2196 const=SERVER_FTP, default=SERVER_HTTP,
2197 dest='server_type',
2198 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002199 self.option_parser.add_option('--tcp-echo', action='store_const',
2200 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2201 dest='server_type',
2202 help='start up a tcp echo server.')
2203 self.option_parser.add_option('--udp-echo', action='store_const',
2204 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2205 dest='server_type',
2206 help='start up a udp echo server.')
Adam Rice9476b8c2018-08-02 15:28:43 +00002207 self.option_parser.add_option('--proxy', action='store_const',
2208 const=SERVER_PROXY,
2209 default=SERVER_HTTP, dest='server_type',
2210 help='start up a proxy server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002211 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2212 const=SERVER_BASIC_AUTH_PROXY,
2213 default=SERVER_HTTP, dest='server_type',
2214 help='start up a proxy server which requires '
2215 'basic authentication.')
2216 self.option_parser.add_option('--websocket', action='store_const',
2217 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2218 dest='server_type',
2219 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002220 self.option_parser.add_option('--https', action='store_true',
2221 dest='https', help='Specify that https '
2222 'should be used.')
2223 self.option_parser.add_option('--cert-and-key-file',
2224 dest='cert_and_key_file', help='specify the '
2225 'path to the file containing the certificate '
2226 'and private key for the server in PEM '
2227 'format')
mattm10ede842016-11-29 11:57:16 -08002228 self.option_parser.add_option('--aia-intermediate', action='store_true',
2229 dest='aia_intermediate',
2230 help='generate a certificate chain that '
2231 'requires AIA cert fetching, and run a '
2232 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002233 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2234 help='The type of OCSP response generated '
2235 'for the automatically generated '
2236 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002237 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2238 default='valid', help='The validity of the '
2239 'range between thisUpdate and nextUpdate')
2240 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2241 default='valid', help='producedAt relative '
2242 'to certificate expiry')
Matt Mueller55aef642018-05-02 18:53:57 +00002243 self.option_parser.add_option('--ocsp-intermediate',
2244 dest='ocsp_intermediate', default=None,
2245 help='If specified, the automatically '
2246 'generated chain will include an '
2247 'intermediate certificate with this type '
2248 'of OCSP response (see docs for --ocsp)')
2249 self.option_parser.add_option('--ocsp-intermediate-date',
2250 dest='ocsp_intermediate_date',
2251 default='valid', help='The validity of the '
2252 'range between thisUpdate and nextUpdate')
2253 self.option_parser.add_option('--ocsp-intermediate-produced',
2254 dest='ocsp_intermediate_produced',
2255 default='valid', help='producedAt relative '
2256 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002257 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2258 default=0, type=int,
2259 help='If non-zero then the generated '
2260 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002261 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2262 default="127.0.0.1",
2263 help='The generated certificate will have '
2264 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002265 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2266 default='0', type='int',
2267 help='If nonzero, certain TLS connections '
2268 'will be aborted in order to test version '
2269 'fallback. 1 means all TLS versions will be '
2270 'aborted. 2 means TLS 1.1 or higher will be '
2271 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002272 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002273 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002274 self.option_parser.add_option('--tls-intolerance-type',
2275 dest='tls_intolerance_type',
2276 default="alert",
2277 help='Controls how the server reacts to a '
2278 'TLS version it is intolerant to. Valid '
2279 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002280 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2281 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002282 default='',
2283 help='Base64 encoded SCT list. If set, '
2284 'server will respond with a '
2285 'signed_certificate_timestamp TLS extension '
2286 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002287 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2288 default=False, const=True,
2289 action='store_const',
2290 help='If given, TLS_FALLBACK_SCSV support '
2291 'will be enabled. This causes the server to '
2292 'reject fallback connections from compatible '
2293 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002294 self.option_parser.add_option('--staple-ocsp-response',
2295 dest='staple_ocsp_response',
2296 default=False, action='store_true',
2297 help='If set, server will staple the OCSP '
2298 'response whenever OCSP is on and the client '
2299 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002300 self.option_parser.add_option('--https-record-resume',
2301 dest='record_resume', const=True,
2302 default=False, action='store_const',
2303 help='Record resumption cache events rather '
2304 'than resuming as normal. Allows the use of '
2305 'the /ssl-session-cache request')
2306 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2307 help='Require SSL client auth on every '
2308 'connection.')
2309 self.option_parser.add_option('--ssl-client-ca', action='append',
2310 default=[], help='Specify that the client '
2311 'certificate request should include the CA '
2312 'named in the subject of the DER-encoded '
2313 'certificate contained in the specified '
2314 'file. This option may appear multiple '
2315 'times, indicating multiple CA names should '
2316 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002317 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2318 default=[], help='Specify that the client '
2319 'certificate request should include the '
2320 'specified certificate_type value. This '
2321 'option may appear multiple times, '
2322 'indicating multiple values should be send '
2323 'in the request. Valid values are '
2324 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2325 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002326 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2327 help='Specify the bulk encryption '
2328 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002329 'SSL server. Valid values are "aes128gcm", '
2330 '"aes256", "aes128", "3des", "rc4". If '
2331 'omitted, all algorithms will be used. This '
2332 'option may appear multiple times, '
2333 'indicating multiple algorithms should be '
2334 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002335 self.option_parser.add_option('--ssl-key-exchange', action='append',
2336 help='Specify the key exchange algorithm(s)'
2337 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002338 'Valid values are "rsa", "dhe_rsa", '
2339 '"ecdhe_rsa". If omitted, all algorithms '
2340 'will be used. This option may appear '
2341 'multiple times, indicating multiple '
2342 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002343 self.option_parser.add_option('--alpn-protocols', action='append',
2344 help='Specify the list of ALPN protocols. '
2345 'The server will not send an ALPN response '
2346 'if this list does not overlap with the '
2347 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002348 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002349 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002350 'an NPN response. The server will not'
2351 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002352 self.option_parser.add_option('--file-root-url', default='/files/',
2353 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002354 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2355 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2356 dest='ws_basic_auth',
2357 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002358 self.option_parser.add_option('--ocsp-server-unavailable',
2359 dest='ocsp_server_unavailable',
2360 default=False, action='store_true',
2361 help='If set, the OCSP server will return '
2362 'a tryLater status rather than the actual '
2363 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002364 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2365 type='int', dest='ocsp_proxy_port_number',
2366 help='Port allocated for OCSP proxy '
2367 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002368 self.option_parser.add_option('--alert-after-handshake',
2369 dest='alert_after_handshake',
2370 default=False, action='store_true',
2371 help='If set, the server will send a fatal '
2372 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002373 self.option_parser.add_option('--no-anonymous-ftp-user',
2374 dest='no_anonymous_ftp_user',
2375 default=False, action='store_true',
2376 help='If set, the FTP server will not create '
2377 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002378 self.option_parser.add_option('--disable-channel-id', action='store_true')
2379 self.option_parser.add_option('--disable-extended-master-secret',
2380 action='store_true')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002381 self.option_parser.add_option('--redirect-connect-to-localhost',
2382 dest='redirect_connect_to_localhost',
2383 default=False, action='store_true',
2384 help='If set, the Proxy server will connect '
2385 'to localhost instead of the requested URL '
2386 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002387
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002388
initial.commit94958cf2008-07-26 22:42:52 +00002389if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002390 sys.exit(ServerRunner().main())