blob: 471455607c674ee3ed8b2883be6baa92745cdd2b [file] [log] [blame]
dpranke@chromium.org70049b72011-10-14 00:38:18 +00001#!/usr/bin/env python
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00002# Copyright 2013 The Chromium Authors. All rights reserved.
license.botf3378c22008-08-24 00:55:55 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00006"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
initial.commit94958cf2008-07-26 22:42:52 +00008
9It supports several test URLs, as specified by the handlers in TestPageHandler.
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +000010By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
initial.commit94958cf2008-07-26 22:42:52 +000013It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
initial.commit94958cf2008-07-26 22:42:52 +000015"""
16
17import base64
18import BaseHTTPServer
19import cgi
mattm@chromium.org11f17fb2012-09-23 00:06:27 +000020import hashlib
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000021import logging
agl@chromium.org77a9ad92012-03-20 15:14:27 +000022import minica
initial.commit94958cf2008-07-26 22:42:52 +000023import os
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +000024import json
rtenneti@chromium.org922a8222011-08-16 03:30:45 +000025import random
initial.commit94958cf2008-07-26 22:42:52 +000026import re
akalin@chromium.org4e4f3c92010-11-27 04:04:52 +000027import select
agl@chromium.orgb3ec3462012-03-19 20:32:47 +000028import socket
agl@chromium.org77a9ad92012-03-20 15:14:27 +000029import SocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000030import ssl
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +000031import struct
agl@chromium.org77a9ad92012-03-20 15:14:27 +000032import sys
33import threading
initial.commit94958cf2008-07-26 22:42:52 +000034import time
battre@chromium.orgd4479e12011-11-07 17:09:19 +000035import urllib
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +000036import urlparse
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +000037import zlib
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +000038
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000039BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(BASE_DIR)))
41
davidben@chromium.org7d53b542014-04-10 17:56:44 +000042# Insert at the beginning of the path, we want to use our copies of the library
Robert Iannucci0e7ec952018-01-18 22:44:16 +000043# unconditionally (since they contain modifications from anything that might be
44# obtained from e.g. PyPi).
Nick Harperfdcd4b32018-01-23 19:59:28 +000045sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000046sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000047sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
48
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000049import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000050from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000051# import manually
52mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000053
davidben@chromium.org7d53b542014-04-10 17:56:44 +000054import pyftpdlib.ftpserver
55
56import tlslite
57import tlslite.api
58
59import echo_message
60import testserver_base
61
maruel@chromium.org756cf982009-03-05 12:46:38 +000062SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000063SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000064SERVER_TCP_ECHO = 2
65SERVER_UDP_ECHO = 3
66SERVER_BASIC_AUTH_PROXY = 4
67SERVER_WEBSOCKET = 5
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
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000128class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
129 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000130 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000131 """This is a specialization of HTTPServer that serves an
132 OCSP response"""
133
134 def serve_forever_on_thread(self):
135 self.thread = threading.Thread(target = self.serve_forever,
136 name = "OCSPServerThread")
137 self.thread.start()
138
139 def stop_serving(self):
140 self.shutdown()
141 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000142
mattm@chromium.org830a3712012-11-07 23:00:07 +0000143
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000144class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000145 testserver_base.ClientRestrictingServerMixIn,
146 testserver_base.BrokenPipeHandlerMixIn,
147 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000148 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000149 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000150
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000151 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000152 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700153 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
154 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000155 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700156 fallback_scsv_enabled, ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -0700157 alert_after_handshake, disable_channel_id, disable_ems,
158 token_binding_params):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000159 self.cert_chain = tlslite.api.X509CertChain()
160 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000161 # Force using only python implementation - otherwise behavior is different
162 # depending on whether m2crypto Python module is present (error is thrown
163 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
164 # the hood.
165 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
166 private=True,
167 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000168 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000169 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000170 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700171 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000172 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000173 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000174 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000175
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000176 if ssl_client_auth:
177 for ca_file in ssl_client_cas:
178 s = open(ca_file).read()
179 x509 = tlslite.api.X509()
180 x509.parse(s)
181 self.ssl_client_cas.append(x509.subject)
182
183 for cert_type in ssl_client_cert_types:
184 self.ssl_client_cert_types.append({
185 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000186 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
187 }[cert_type])
188
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000189 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800190 # Enable SSLv3 for testing purposes.
191 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000192 if ssl_bulk_ciphers is not None:
193 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000194 if ssl_key_exchanges is not None:
195 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000196 if tls_intolerant != 0:
197 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
198 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700199 if alert_after_handshake:
200 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700201 if disable_channel_id:
202 self.ssl_handshake_settings.enableChannelID = False
203 if disable_ems:
204 self.ssl_handshake_settings.enableExtendedMasterSecret = False
205 self.ssl_handshake_settings.supportedTokenBindingParams = \
206 token_binding_params
bnc5fb33bd2016-08-05 12:09:21 -0700207 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000208
rsleevi8146efa2015-03-16 12:31:24 -0700209 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000210 # If record_resume_info is true then we'll replace the session cache with
211 # an object that records the lookups and inserts that it sees.
212 self.session_cache = RecordingSSLSessionCache()
213 else:
214 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000215 testserver_base.StoppableHTTPServer.__init__(self,
216 server_address,
217 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000218
219 def handshake(self, tlsConnection):
220 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000221
initial.commit94958cf2008-07-26 22:42:52 +0000222 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000223 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000224 tlsConnection.handshakeServer(certChain=self.cert_chain,
225 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000226 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000227 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000228 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000229 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000230 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700231 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000232 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000233 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000234 fallbackSCSV=self.fallback_scsv_enabled,
235 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000236 tlsConnection.ignoreAbruptClose = True
237 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000238 except tlslite.api.TLSAbruptCloseError:
239 # Ignore abrupt close.
240 return True
initial.commit94958cf2008-07-26 22:42:52 +0000241 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000242 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000243 return False
244
akalin@chromium.org154bb132010-11-12 02:20:27 +0000245
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000246class FTPServer(testserver_base.ClientRestrictingServerMixIn,
247 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000248 """This is a specialization of FTPServer that adds client verification."""
249
250 pass
251
252
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000253class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
254 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000255 """A TCP echo server that echoes back what it has received."""
256
257 def server_bind(self):
258 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000259
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000260 SocketServer.TCPServer.server_bind(self)
261 host, port = self.socket.getsockname()[:2]
262 self.server_name = socket.getfqdn(host)
263 self.server_port = port
264
265 def serve_forever(self):
266 self.stop = False
267 self.nonce_time = None
268 while not self.stop:
269 self.handle_request()
270 self.socket.close()
271
272
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000273class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
274 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000275 """A UDP echo server that echoes back what it has received."""
276
277 def server_bind(self):
278 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000279
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000280 SocketServer.UDPServer.server_bind(self)
281 host, port = self.socket.getsockname()[:2]
282 self.server_name = socket.getfqdn(host)
283 self.server_port = port
284
285 def serve_forever(self):
286 self.stop = False
287 self.nonce_time = None
288 while not self.stop:
289 self.handle_request()
290 self.socket.close()
291
292
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000293class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000294 # Class variables to allow for persistence state between page handler
295 # invocations
296 rst_limits = {}
297 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000298
299 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000300 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000301 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000302 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000303 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000304 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000305 self.NoCacheMaxAgeTimeHandler,
306 self.NoCacheTimeHandler,
307 self.CacheTimeHandler,
308 self.CacheExpiresHandler,
309 self.CacheProxyRevalidateHandler,
310 self.CachePrivateHandler,
311 self.CachePublicHandler,
312 self.CacheSMaxAgeHandler,
313 self.CacheMustRevalidateHandler,
314 self.CacheMustRevalidateMaxAgeHandler,
315 self.CacheNoStoreHandler,
316 self.CacheNoStoreMaxAgeHandler,
317 self.CacheNoTransformHandler,
318 self.DownloadHandler,
319 self.DownloadFinishHandler,
320 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000321 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000322 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000323 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000324 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000325 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000326 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000327 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000328 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000329 self.AuthBasicHandler,
330 self.AuthDigestHandler,
331 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000332 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000333 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000334 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700335 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000336 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000337 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000338 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000339 self.GetChannelID,
nharper08eae822016-01-25 15:54:14 -0800340 self.GetTokenBindingEKM,
nharpercb1adc32016-03-30 16:05:48 -0700341 self.ForwardTokenBindingHeader,
pneubeckfd4f0442015-08-07 04:55:10 -0700342 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700343 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000344 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000345 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000346 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000347 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000348 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000349 self.PostOnlyFileHandler,
350 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000351 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000352 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000353 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000354 head_handlers = [
355 self.FileHandler,
356 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000357
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000359 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000360 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 'gif': 'image/gif',
362 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000363 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700364 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000365 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000366 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000367 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000368 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000369 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000370 }
initial.commit94958cf2008-07-26 22:42:52 +0000371 self._default_mime_type = 'text/html'
372
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000373 testserver_base.BasePageHandler.__init__(self, request, client_address,
374 socket_server, connect_handlers,
375 get_handlers, head_handlers,
376 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000377
initial.commit94958cf2008-07-26 22:42:52 +0000378 def GetMIMETypeFromName(self, file_name):
379 """Returns the mime type for the specified file_name. So far it only looks
380 at the file extension."""
381
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000382 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000383 if len(extension) == 0:
384 # no extension.
385 return self._default_mime_type
386
ericroman@google.comc17ca532009-05-07 03:51:05 +0000387 # extension starts with a dot, so we need to remove it
388 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000389
initial.commit94958cf2008-07-26 22:42:52 +0000390 def NoCacheMaxAgeTimeHandler(self):
391 """This request handler yields a page with the title set to the current
392 system time, and no caching requested."""
393
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000394 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000395 return False
396
397 self.send_response(200)
398 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000399 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000400 self.end_headers()
401
maruel@google.come250a9b2009-03-10 17:39:46 +0000402 self.wfile.write('<html><head><title>%s</title></head></html>' %
403 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000404
405 return True
406
407 def NoCacheTimeHandler(self):
408 """This request handler yields a page with the title set to the current
409 system time, and no caching requested."""
410
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000411 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000412 return False
413
414 self.send_response(200)
415 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000416 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000417 self.end_headers()
418
maruel@google.come250a9b2009-03-10 17:39:46 +0000419 self.wfile.write('<html><head><title>%s</title></head></html>' %
420 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000421
422 return True
423
424 def CacheTimeHandler(self):
425 """This request handler yields a page with the title set to the current
426 system time, and allows caching for one minute."""
427
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000428 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000429 return False
430
431 self.send_response(200)
432 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000433 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000434 self.end_headers()
435
maruel@google.come250a9b2009-03-10 17:39:46 +0000436 self.wfile.write('<html><head><title>%s</title></head></html>' %
437 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000438
439 return True
440
441 def CacheExpiresHandler(self):
442 """This request handler yields a page with the title set to the current
443 system time, and set the page to expire on 1 Jan 2099."""
444
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000445 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000446 return False
447
448 self.send_response(200)
449 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000450 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000451 self.end_headers()
452
maruel@google.come250a9b2009-03-10 17:39:46 +0000453 self.wfile.write('<html><head><title>%s</title></head></html>' %
454 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000455
456 return True
457
458 def CacheProxyRevalidateHandler(self):
459 """This request handler yields a page with the title set to the current
460 system time, and allows caching for 60 seconds"""
461
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000462 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000463 return False
464
465 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000466 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000467 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
468 self.end_headers()
469
maruel@google.come250a9b2009-03-10 17:39:46 +0000470 self.wfile.write('<html><head><title>%s</title></head></html>' %
471 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000472
473 return True
474
475 def CachePrivateHandler(self):
476 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700477 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000478
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000479 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000480 return False
481
482 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000483 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000484 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000485 self.end_headers()
486
maruel@google.come250a9b2009-03-10 17:39:46 +0000487 self.wfile.write('<html><head><title>%s</title></head></html>' %
488 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000489
490 return True
491
492 def CachePublicHandler(self):
493 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700494 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000495
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000496 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000497 return False
498
499 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000500 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000501 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000502 self.end_headers()
503
maruel@google.come250a9b2009-03-10 17:39:46 +0000504 self.wfile.write('<html><head><title>%s</title></head></html>' %
505 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000506
507 return True
508
509 def CacheSMaxAgeHandler(self):
510 """This request handler yields a page with the title set to the current
511 system time, and does not allow for caching."""
512
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000513 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000514 return False
515
516 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000517 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000518 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
519 self.end_headers()
520
maruel@google.come250a9b2009-03-10 17:39:46 +0000521 self.wfile.write('<html><head><title>%s</title></head></html>' %
522 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000523
524 return True
525
526 def CacheMustRevalidateHandler(self):
527 """This request handler yields a page with the title set to the current
528 system time, and does not allow caching."""
529
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000530 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000531 return False
532
533 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000534 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000535 self.send_header('Cache-Control', 'must-revalidate')
536 self.end_headers()
537
maruel@google.come250a9b2009-03-10 17:39:46 +0000538 self.wfile.write('<html><head><title>%s</title></head></html>' %
539 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000540
541 return True
542
543 def CacheMustRevalidateMaxAgeHandler(self):
544 """This request handler yields a page with the title set to the current
545 system time, and does not allow caching event though max-age of 60
546 seconds is specified."""
547
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000548 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000549 return False
550
551 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000552 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000553 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
554 self.end_headers()
555
maruel@google.come250a9b2009-03-10 17:39:46 +0000556 self.wfile.write('<html><head><title>%s</title></head></html>' %
557 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000558
559 return True
560
initial.commit94958cf2008-07-26 22:42:52 +0000561 def CacheNoStoreHandler(self):
562 """This request handler yields a page with the title set to the current
563 system time, and does not allow the page to be stored."""
564
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000565 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000566 return False
567
568 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000569 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000570 self.send_header('Cache-Control', 'no-store')
571 self.end_headers()
572
maruel@google.come250a9b2009-03-10 17:39:46 +0000573 self.wfile.write('<html><head><title>%s</title></head></html>' %
574 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000575
576 return True
577
578 def CacheNoStoreMaxAgeHandler(self):
579 """This request handler yields a page with the title set to the current
580 system time, and does not allow the page to be stored even though max-age
581 of 60 seconds is specified."""
582
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000583 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000584 return False
585
586 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000587 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000588 self.send_header('Cache-Control', 'max-age=60, no-store')
589 self.end_headers()
590
maruel@google.come250a9b2009-03-10 17:39:46 +0000591 self.wfile.write('<html><head><title>%s</title></head></html>' %
592 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000593
594 return True
595
596
597 def CacheNoTransformHandler(self):
598 """This request handler yields a page with the title set to the current
599 system time, and does not allow the content to transformed during
600 user-agent caching"""
601
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000602 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000603 return False
604
605 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000606 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000607 self.send_header('Cache-Control', 'no-transform')
608 self.end_headers()
609
maruel@google.come250a9b2009-03-10 17:39:46 +0000610 self.wfile.write('<html><head><title>%s</title></head></html>' %
611 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000612
613 return True
614
615 def EchoHeader(self):
616 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000617
ananta@chromium.org219b2062009-10-23 16:09:41 +0000618 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000619
ananta@chromium.org56812d02011-04-07 17:52:05 +0000620 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000621 """This function echoes back the value of a specific request header while
622 allowing caching for 16 hours."""
623
ananta@chromium.org56812d02011-04-07 17:52:05 +0000624 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000625
626 def EchoHeaderHelper(self, echo_header):
627 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000628
ananta@chromium.org219b2062009-10-23 16:09:41 +0000629 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000630 return False
631
632 query_char = self.path.find('?')
633 if query_char != -1:
634 header_name = self.path[query_char+1:]
635
636 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000637 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000638 if echo_header == '/echoheadercache':
639 self.send_header('Cache-control', 'max-age=60000')
640 else:
641 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000642 # insert a vary header to properly indicate that the cachability of this
643 # request is subject to value of the request header being echoed.
644 if len(header_name) > 0:
645 self.send_header('Vary', header_name)
646 self.end_headers()
647
648 if len(header_name) > 0:
649 self.wfile.write(self.headers.getheader(header_name))
650
651 return True
652
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000653 def ReadRequestBody(self):
654 """This function reads the body of the current HTTP request, handling
655 both plain and chunked transfer encoded requests."""
656
657 if self.headers.getheader('transfer-encoding') != 'chunked':
658 length = int(self.headers.getheader('content-length'))
659 return self.rfile.read(length)
660
661 # Read the request body as chunks.
662 body = ""
663 while True:
664 line = self.rfile.readline()
665 length = int(line, 16)
666 if length == 0:
667 self.rfile.readline()
668 break
669 body += self.rfile.read(length)
670 self.rfile.read(2)
671 return body
672
initial.commit94958cf2008-07-26 22:42:52 +0000673 def EchoHandler(self):
674 """This handler just echoes back the payload of the request, for testing
675 form submission."""
676
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000677 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000678 return False
679
hirono2838c572015-01-21 12:18:11 -0800680 _, _, _, _, query, _ = urlparse.urlparse(self.path)
681 query_params = cgi.parse_qs(query, True)
682 if 'status' in query_params:
683 self.send_response(int(query_params['status'][0]))
684 else:
685 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000686 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000687 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000688 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000689 return True
690
691 def EchoTitleHandler(self):
692 """This handler is like Echo, but sets the page title to the request."""
693
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000694 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000695 return False
696
697 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000698 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000699 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000700 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000701 self.wfile.write('<html><head><title>')
702 self.wfile.write(request)
703 self.wfile.write('</title></head></html>')
704 return True
705
706 def EchoAllHandler(self):
707 """This handler yields a (more) human-readable page listing information
708 about the request header & contents."""
709
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000710 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000711 return False
712
713 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000714 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000715 self.end_headers()
716 self.wfile.write('<html><head><style>'
717 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
718 '</style></head><body>'
719 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000720 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000721 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000722
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000723 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000724 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000725 params = cgi.parse_qs(qs, keep_blank_values=1)
726
727 for param in params:
728 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000729
730 self.wfile.write('</pre>')
731
732 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
733
734 self.wfile.write('</body></html>')
735 return True
736
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000737 def EchoMultipartPostHandler(self):
738 """This handler echoes received multipart post data as json format."""
739
740 if not (self._ShouldHandleRequest("/echomultipartpost") or
741 self._ShouldHandleRequest("/searchbyimage")):
742 return False
743
744 content_type, parameters = cgi.parse_header(
745 self.headers.getheader('content-type'))
746 if content_type == 'multipart/form-data':
747 post_multipart = cgi.parse_multipart(self.rfile, parameters)
748 elif content_type == 'application/x-www-form-urlencoded':
749 raise Exception('POST by application/x-www-form-urlencoded is '
750 'not implemented.')
751 else:
752 post_multipart = {}
753
754 # Since the data can be binary, we encode them by base64.
755 post_multipart_base64_encoded = {}
756 for field, values in post_multipart.items():
757 post_multipart_base64_encoded[field] = [base64.b64encode(value)
758 for value in values]
759
760 result = {'POST_multipart' : post_multipart_base64_encoded}
761
762 self.send_response(200)
763 self.send_header("Content-type", "text/plain")
764 self.end_headers()
765 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
766 return True
767
initial.commit94958cf2008-07-26 22:42:52 +0000768 def DownloadHandler(self):
769 """This handler sends a downloadable file with or without reporting
770 the size (6K)."""
771
772 if self.path.startswith("/download-unknown-size"):
773 send_length = False
774 elif self.path.startswith("/download-known-size"):
775 send_length = True
776 else:
777 return False
778
779 #
780 # The test which uses this functionality is attempting to send
781 # small chunks of data to the client. Use a fairly large buffer
782 # so that we'll fill chrome's IO buffer enough to force it to
783 # actually write the data.
784 # See also the comments in the client-side of this test in
785 # download_uitest.cc
786 #
787 size_chunk1 = 35*1024
788 size_chunk2 = 10*1024
789
790 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000791 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000792 self.send_header('Cache-Control', 'max-age=0')
793 if send_length:
794 self.send_header('Content-Length', size_chunk1 + size_chunk2)
795 self.end_headers()
796
797 # First chunk of data:
798 self.wfile.write("*" * size_chunk1)
799 self.wfile.flush()
800
801 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000802 self.server.wait_for_download = True
803 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000804 self.server.handle_request()
805
806 # Second chunk of data:
807 self.wfile.write("*" * size_chunk2)
808 return True
809
810 def DownloadFinishHandler(self):
811 """This handler just tells the server to finish the current download."""
812
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000813 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000814 return False
815
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000816 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000817 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000818 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000819 self.send_header('Cache-Control', 'max-age=0')
820 self.end_headers()
821 return True
822
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000823 def _ReplaceFileData(self, data, query_parameters):
824 """Replaces matching substrings in a file.
825
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000826 If the 'replace_text' URL query parameter is present, it is expected to be
827 of the form old_text:new_text, which indicates that any old_text strings in
828 the file are replaced with new_text. Multiple 'replace_text' parameters may
829 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000830
831 If the parameters are not present, |data| is returned.
832 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000833
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000834 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000835 replace_text_values = query_dict.get('replace_text', [])
836 for replace_text_value in replace_text_values:
837 replace_text_args = replace_text_value.split(':')
838 if len(replace_text_args) != 2:
839 raise ValueError(
840 'replace_text must be of form old_text:new_text. Actual value: %s' %
841 replace_text_value)
842 old_text_b64, new_text_b64 = replace_text_args
843 old_text = base64.urlsafe_b64decode(old_text_b64)
844 new_text = base64.urlsafe_b64decode(new_text_b64)
845 data = data.replace(old_text, new_text)
846 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000847
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000848 def ZipFileHandler(self):
849 """This handler sends the contents of the requested file in compressed form.
850 Can pass in a parameter that specifies that the content length be
851 C - the compressed size (OK),
852 U - the uncompressed size (Non-standard, but handled),
853 S - less than compressed (OK because we keep going),
854 M - larger than compressed but less than uncompressed (an error),
855 L - larger than uncompressed (an error)
856 Example: compressedfiles/Picture_1.doc?C
857 """
858
859 prefix = "/compressedfiles/"
860 if not self.path.startswith(prefix):
861 return False
862
863 # Consume a request body if present.
864 if self.command == 'POST' or self.command == 'PUT' :
865 self.ReadRequestBody()
866
867 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
868
869 if not query in ('C', 'U', 'S', 'M', 'L'):
870 return False
871
872 sub_path = url_path[len(prefix):]
873 entries = sub_path.split('/')
874 file_path = os.path.join(self.server.data_dir, *entries)
875 if os.path.isdir(file_path):
876 file_path = os.path.join(file_path, 'index.html')
877
878 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000879 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000880 self.send_error(404)
881 return True
882
883 f = open(file_path, "rb")
884 data = f.read()
885 uncompressed_len = len(data)
886 f.close()
887
888 # Compress the data.
889 data = zlib.compress(data)
890 compressed_len = len(data)
891
892 content_length = compressed_len
893 if query == 'U':
894 content_length = uncompressed_len
895 elif query == 'S':
896 content_length = compressed_len / 2
897 elif query == 'M':
898 content_length = (compressed_len + uncompressed_len) / 2
899 elif query == 'L':
900 content_length = compressed_len + uncompressed_len
901
902 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000903 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000904 self.send_header('Content-encoding', 'deflate')
905 self.send_header('Connection', 'close')
906 self.send_header('Content-Length', content_length)
907 self.send_header('ETag', '\'' + file_path + '\'')
908 self.end_headers()
909
910 self.wfile.write(data)
911
912 return True
913
initial.commit94958cf2008-07-26 22:42:52 +0000914 def FileHandler(self):
915 """This handler sends the contents of the requested file. Wow, it's like
916 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000917
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000918 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000919 if not self.path.startswith(prefix):
920 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000921 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000922
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000923 def PostOnlyFileHandler(self):
924 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000925
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000926 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000927 if not self.path.startswith(prefix):
928 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000929 return self._FileHandlerHelper(prefix)
930
931 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000932 request_body = ''
933 if self.command == 'POST' or self.command == 'PUT':
934 # Consume a request body if present.
935 request_body = self.ReadRequestBody()
936
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000937 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000938 query_dict = cgi.parse_qs(query)
939
940 expected_body = query_dict.get('expected_body', [])
941 if expected_body and request_body not in expected_body:
942 self.send_response(404)
943 self.end_headers()
944 self.wfile.write('')
945 return True
946
947 expected_headers = query_dict.get('expected_headers', [])
948 for expected_header in expected_headers:
949 header_name, expected_value = expected_header.split(':')
950 if self.headers.getheader(header_name) != expected_value:
951 self.send_response(404)
952 self.end_headers()
953 self.wfile.write('')
954 return True
955
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000956 sub_path = url_path[len(prefix):]
957 entries = sub_path.split('/')
958 file_path = os.path.join(self.server.data_dir, *entries)
959 if os.path.isdir(file_path):
960 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000961
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000962 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000963 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000964 self.send_error(404)
965 return True
966
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000967 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000968 data = f.read()
969 f.close()
970
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000971 data = self._ReplaceFileData(data, query)
972
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000973 old_protocol_version = self.protocol_version
974
initial.commit94958cf2008-07-26 22:42:52 +0000975 # If file.mock-http-headers exists, it contains the headers we
976 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000977 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000978 if os.path.isfile(headers_path):
979 f = open(headers_path, "r")
980
981 # "HTTP/1.1 200 OK"
982 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000983 http_major, http_minor, status_code = re.findall(
984 'HTTP/(\d+).(\d+) (\d+)', response)[0]
985 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000986 self.send_response(int(status_code))
987
988 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000989 header_values = re.findall('(\S+):\s*(.*)', line)
990 if len(header_values) > 0:
991 # "name: value"
992 name, value = header_values[0]
993 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000994 f.close()
995 else:
996 # Could be more generic once we support mime-type sniffing, but for
997 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000998
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000999 range_header = self.headers.get('Range')
1000 if range_header and range_header.startswith('bytes='):
1001 # Note this doesn't handle all valid byte range_header values (i.e.
1002 # left open ended ones), just enough for what we needed so far.
1003 range_header = range_header[6:].split('-')
1004 start = int(range_header[0])
1005 if range_header[1]:
1006 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001007 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001008 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001009
1010 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001011 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1012 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001013 self.send_header('Content-Range', content_range)
1014 data = data[start: end + 1]
1015 else:
1016 self.send_response(200)
1017
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001018 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001019 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001020 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001021 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001022 self.end_headers()
1023
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001024 if (self.command != 'HEAD'):
1025 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001026
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001027 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001028 return True
1029
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001030 def SetCookieHandler(self):
1031 """This handler just sets a cookie, for testing cookie handling."""
1032
1033 if not self._ShouldHandleRequest("/set-cookie"):
1034 return False
1035
1036 query_char = self.path.find('?')
1037 if query_char != -1:
1038 cookie_values = self.path[query_char + 1:].split('&')
1039 else:
1040 cookie_values = ("",)
1041 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001042 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001043 for cookie_value in cookie_values:
1044 self.send_header('Set-Cookie', '%s' % cookie_value)
1045 self.end_headers()
1046 for cookie_value in cookie_values:
1047 self.wfile.write('%s' % cookie_value)
1048 return True
1049
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001050 def SetManyCookiesHandler(self):
1051 """This handler just sets a given number of cookies, for testing handling
1052 of large numbers of cookies."""
1053
1054 if not self._ShouldHandleRequest("/set-many-cookies"):
1055 return False
1056
1057 query_char = self.path.find('?')
1058 if query_char != -1:
1059 num_cookies = int(self.path[query_char + 1:])
1060 else:
1061 num_cookies = 0
1062 self.send_response(200)
1063 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001064 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001065 self.send_header('Set-Cookie', 'a=')
1066 self.end_headers()
1067 self.wfile.write('%d cookies were sent' % num_cookies)
1068 return True
1069
mattm@chromium.org983fc462012-06-30 00:52:08 +00001070 def ExpectAndSetCookieHandler(self):
1071 """Expects some cookies to be sent, and if they are, sets more cookies.
1072
1073 The expect parameter specifies a required cookie. May be specified multiple
1074 times.
1075 The set parameter specifies a cookie to set if all required cookies are
1076 preset. May be specified multiple times.
1077 The data parameter specifies the response body data to be returned."""
1078
1079 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1080 return False
1081
1082 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1083 query_dict = cgi.parse_qs(query)
1084 cookies = set()
1085 if 'Cookie' in self.headers:
1086 cookie_header = self.headers.getheader('Cookie')
1087 cookies.update([s.strip() for s in cookie_header.split(';')])
1088 got_all_expected_cookies = True
1089 for expected_cookie in query_dict.get('expect', []):
1090 if expected_cookie not in cookies:
1091 got_all_expected_cookies = False
1092 self.send_response(200)
1093 self.send_header('Content-Type', 'text/html')
1094 if got_all_expected_cookies:
1095 for cookie_value in query_dict.get('set', []):
1096 self.send_header('Set-Cookie', '%s' % cookie_value)
1097 self.end_headers()
1098 for data_value in query_dict.get('data', []):
1099 self.wfile.write(data_value)
1100 return True
1101
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001102 def SetHeaderHandler(self):
1103 """This handler sets a response header. Parameters are in the
1104 key%3A%20value&key2%3A%20value2 format."""
1105
1106 if not self._ShouldHandleRequest("/set-header"):
1107 return False
1108
1109 query_char = self.path.find('?')
1110 if query_char != -1:
1111 headers_values = self.path[query_char + 1:].split('&')
1112 else:
1113 headers_values = ("",)
1114 self.send_response(200)
1115 self.send_header('Content-Type', 'text/html')
1116 for header_value in headers_values:
1117 header_value = urllib.unquote(header_value)
1118 (key, value) = header_value.split(': ', 1)
1119 self.send_header(key, value)
1120 self.end_headers()
1121 for header_value in headers_values:
1122 self.wfile.write('%s' % header_value)
1123 return True
1124
initial.commit94958cf2008-07-26 22:42:52 +00001125 def AuthBasicHandler(self):
1126 """This handler tests 'Basic' authentication. It just sends a page with
1127 title 'user/pass' if you succeed."""
1128
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001129 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001130 return False
1131
1132 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001133 expected_password = 'secret'
1134 realm = 'testrealm'
1135 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001136
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001137 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1138 query_params = cgi.parse_qs(query, True)
1139 if 'set-cookie-if-challenged' in query_params:
1140 set_cookie_if_challenged = True
1141 if 'password' in query_params:
1142 expected_password = query_params['password'][0]
1143 if 'realm' in query_params:
1144 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001145
initial.commit94958cf2008-07-26 22:42:52 +00001146 auth = self.headers.getheader('authorization')
1147 try:
1148 if not auth:
1149 raise Exception('no auth')
1150 b64str = re.findall(r'Basic (\S+)', auth)[0]
1151 userpass = base64.b64decode(b64str)
1152 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001153 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001154 raise Exception('wrong password')
1155 except Exception, e:
1156 # Authentication failed.
1157 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001158 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001159 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001160 if set_cookie_if_challenged:
1161 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001162 self.end_headers()
1163 self.wfile.write('<html><head>')
1164 self.wfile.write('<title>Denied: %s</title>' % e)
1165 self.wfile.write('</head><body>')
1166 self.wfile.write('auth=%s<p>' % auth)
1167 self.wfile.write('b64str=%s<p>' % b64str)
1168 self.wfile.write('username: %s<p>' % username)
1169 self.wfile.write('userpass: %s<p>' % userpass)
1170 self.wfile.write('password: %s<p>' % password)
1171 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1172 self.wfile.write('</body></html>')
1173 return True
1174
1175 # Authentication successful. (Return a cachable response to allow for
1176 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001177 old_protocol_version = self.protocol_version
1178 self.protocol_version = "HTTP/1.1"
1179
initial.commit94958cf2008-07-26 22:42:52 +00001180 if_none_match = self.headers.getheader('if-none-match')
1181 if if_none_match == "abc":
1182 self.send_response(304)
1183 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001184 elif url_path.endswith(".gif"):
1185 # Using chrome/test/data/google/logo.gif as the test image
1186 test_image_path = ['google', 'logo.gif']
1187 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1188 if not os.path.isfile(gif_path):
1189 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001190 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001191 return True
1192
1193 f = open(gif_path, "rb")
1194 data = f.read()
1195 f.close()
1196
1197 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001198 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001199 self.send_header('Cache-control', 'max-age=60000')
1200 self.send_header('Etag', 'abc')
1201 self.end_headers()
1202 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001203 else:
1204 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001205 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001206 self.send_header('Cache-control', 'max-age=60000')
1207 self.send_header('Etag', 'abc')
1208 self.end_headers()
1209 self.wfile.write('<html><head>')
1210 self.wfile.write('<title>%s/%s</title>' % (username, password))
1211 self.wfile.write('</head><body>')
1212 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001213 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001214 self.wfile.write('</body></html>')
1215
rvargas@google.com54453b72011-05-19 01:11:11 +00001216 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001217 return True
1218
tonyg@chromium.org75054202010-03-31 22:06:10 +00001219 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001220 """Returns a nonce that's stable per request path for the server's lifetime.
1221 This is a fake implementation. A real implementation would only use a given
1222 nonce a single time (hence the name n-once). However, for the purposes of
1223 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001224
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001225 Args:
1226 force_reset: Iff set, the nonce will be changed. Useful for testing the
1227 "stale" response.
1228 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001229
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001230 if force_reset or not self.server.nonce_time:
1231 self.server.nonce_time = time.time()
1232 return hashlib.md5('privatekey%s%d' %
1233 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001234
1235 def AuthDigestHandler(self):
1236 """This handler tests 'Digest' authentication.
1237
1238 It just sends a page with title 'user/pass' if you succeed.
1239
1240 A stale response is sent iff "stale" is present in the request path.
1241 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001242
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001243 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001244 return False
1245
tonyg@chromium.org75054202010-03-31 22:06:10 +00001246 stale = 'stale' in self.path
1247 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001248 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001249 password = 'secret'
1250 realm = 'testrealm'
1251
1252 auth = self.headers.getheader('authorization')
1253 pairs = {}
1254 try:
1255 if not auth:
1256 raise Exception('no auth')
1257 if not auth.startswith('Digest'):
1258 raise Exception('not digest')
1259 # Pull out all the name="value" pairs as a dictionary.
1260 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1261
1262 # Make sure it's all valid.
1263 if pairs['nonce'] != nonce:
1264 raise Exception('wrong nonce')
1265 if pairs['opaque'] != opaque:
1266 raise Exception('wrong opaque')
1267
1268 # Check the 'response' value and make sure it matches our magic hash.
1269 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001270 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001271 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001272 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001273 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001274 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001275 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1276 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001277 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001278
1279 if pairs['response'] != response:
1280 raise Exception('wrong password')
1281 except Exception, e:
1282 # Authentication failed.
1283 self.send_response(401)
1284 hdr = ('Digest '
1285 'realm="%s", '
1286 'domain="/", '
1287 'qop="auth", '
1288 'algorithm=MD5, '
1289 'nonce="%s", '
1290 'opaque="%s"') % (realm, nonce, opaque)
1291 if stale:
1292 hdr += ', stale="TRUE"'
1293 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001294 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001295 self.end_headers()
1296 self.wfile.write('<html><head>')
1297 self.wfile.write('<title>Denied: %s</title>' % e)
1298 self.wfile.write('</head><body>')
1299 self.wfile.write('auth=%s<p>' % auth)
1300 self.wfile.write('pairs=%s<p>' % pairs)
1301 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1302 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1303 self.wfile.write('</body></html>')
1304 return True
1305
1306 # Authentication successful.
1307 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001308 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001309 self.end_headers()
1310 self.wfile.write('<html><head>')
1311 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1312 self.wfile.write('</head><body>')
1313 self.wfile.write('auth=%s<p>' % auth)
1314 self.wfile.write('pairs=%s<p>' % pairs)
1315 self.wfile.write('</body></html>')
1316
1317 return True
1318
1319 def SlowServerHandler(self):
1320 """Wait for the user suggested time before responding. The syntax is
1321 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001322
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001323 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001324 return False
1325 query_char = self.path.find('?')
1326 wait_sec = 1.0
1327 if query_char >= 0:
1328 try:
davidben05f82202015-03-31 13:48:07 -07001329 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001330 except ValueError:
1331 pass
1332 time.sleep(wait_sec)
1333 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001334 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001335 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001336 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001337 return True
1338
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001339 def ChunkedServerHandler(self):
1340 """Send chunked response. Allows to specify chunks parameters:
1341 - waitBeforeHeaders - ms to wait before sending headers
1342 - waitBetweenChunks - ms to wait between chunks
1343 - chunkSize - size of each chunk in bytes
1344 - chunksNumber - number of chunks
1345 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1346 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001347
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001348 if not self._ShouldHandleRequest("/chunked"):
1349 return False
1350 query_char = self.path.find('?')
1351 chunkedSettings = {'waitBeforeHeaders' : 0,
1352 'waitBetweenChunks' : 0,
1353 'chunkSize' : 5,
1354 'chunksNumber' : 5}
1355 if query_char >= 0:
1356 params = self.path[query_char + 1:].split('&')
1357 for param in params:
1358 keyValue = param.split('=')
1359 if len(keyValue) == 2:
1360 try:
1361 chunkedSettings[keyValue[0]] = int(keyValue[1])
1362 except ValueError:
1363 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001364 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001365 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1366 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001367 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001368 self.send_header('Connection', 'close')
1369 self.send_header('Transfer-Encoding', 'chunked')
1370 self.end_headers()
1371 # Chunked encoding: sending all chunks, then final zero-length chunk and
1372 # then final CRLF.
1373 for i in range(0, chunkedSettings['chunksNumber']):
1374 if i > 0:
1375 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1376 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001377 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001378 self.sendChunkHelp('')
1379 return True
1380
creis@google.com2f4f6a42011-03-25 19:44:19 +00001381 def NoContentHandler(self):
1382 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001383
creis@google.com2f4f6a42011-03-25 19:44:19 +00001384 if not self._ShouldHandleRequest("/nocontent"):
1385 return False
1386 self.send_response(204)
1387 self.end_headers()
1388 return True
1389
initial.commit94958cf2008-07-26 22:42:52 +00001390 def ServerRedirectHandler(self):
1391 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001392 '/server-redirect?http://foo.bar/asdf' to redirect to
1393 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001394
1395 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001396 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001397 return False
1398
1399 query_char = self.path.find('?')
1400 if query_char < 0 or len(self.path) <= query_char + 1:
1401 self.sendRedirectHelp(test_name)
1402 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001403 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001404
1405 self.send_response(301) # moved permanently
1406 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001407 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001408 self.end_headers()
1409 self.wfile.write('<html><head>')
1410 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1411
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001412 return True
initial.commit94958cf2008-07-26 22:42:52 +00001413
naskoe7a0d0d2014-09-29 08:53:05 -07001414 def CrossSiteRedirectHandler(self):
1415 """Sends a server redirect to the given site. The syntax is
1416 '/cross-site/hostname/...' to redirect to //hostname/...
1417 It is used to navigate between different Sites, causing
1418 cross-site/cross-process navigations in the browser."""
1419
1420 test_name = "/cross-site"
1421 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001422 return False
1423
1424 params = urllib.unquote(self.path[(len(test_name) + 1):])
1425 slash = params.find('/')
1426 if slash < 0:
1427 self.sendRedirectHelp(test_name)
1428 return True
1429
1430 host = params[:slash]
1431 path = params[(slash+1):]
1432 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1433
1434 self.send_response(301) # moved permanently
1435 self.send_header('Location', dest)
1436 self.send_header('Content-Type', 'text/html')
1437 self.end_headers()
1438 self.wfile.write('<html><head>')
1439 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1440
1441 return True
1442
initial.commit94958cf2008-07-26 22:42:52 +00001443 def ClientRedirectHandler(self):
1444 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001445 '/client-redirect?http://foo.bar/asdf' to redirect to
1446 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001447
1448 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001449 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001450 return False
1451
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001452 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001453 if query_char < 0 or len(self.path) <= query_char + 1:
1454 self.sendRedirectHelp(test_name)
1455 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001456 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001457
1458 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001459 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001460 self.end_headers()
1461 self.wfile.write('<html><head>')
1462 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1463 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1464
1465 return True
1466
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001467 def GetSSLSessionCacheHandler(self):
1468 """Send a reply containing a log of the session cache operations."""
1469
1470 if not self._ShouldHandleRequest('/ssl-session-cache'):
1471 return False
1472
1473 self.send_response(200)
1474 self.send_header('Content-Type', 'text/plain')
1475 self.end_headers()
1476 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001477 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001478 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001479 self.wfile.write('Pass --https-record-resume in order to use' +
1480 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001481 return True
1482
1483 for (action, sessionID) in log:
1484 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001485 return True
1486
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001487 def SSLManySmallRecords(self):
1488 """Sends a reply consisting of a variety of small writes. These will be
1489 translated into a series of small SSL records when used over an HTTPS
1490 server."""
1491
1492 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1493 return False
1494
1495 self.send_response(200)
1496 self.send_header('Content-Type', 'text/plain')
1497 self.end_headers()
1498
1499 # Write ~26K of data, in 1350 byte chunks
1500 for i in xrange(20):
1501 self.wfile.write('*' * 1350)
1502 self.wfile.flush()
1503 return True
1504
agl@chromium.org04700be2013-03-02 18:40:41 +00001505 def GetChannelID(self):
1506 """Send a reply containing the hashed ChannelID that the client provided."""
1507
1508 if not self._ShouldHandleRequest('/channel-id'):
1509 return False
1510
1511 self.send_response(200)
1512 self.send_header('Content-Type', 'text/plain')
1513 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001514 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001515 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1516 return True
1517
nharper08eae822016-01-25 15:54:14 -08001518 def GetTokenBindingEKM(self):
1519 """Send a reply containing the EKM value for token binding from the TLS
1520 layer."""
1521
1522 if not self._ShouldHandleRequest('/tokbind-ekm'):
1523 return False
1524
1525 ekm = self.server.tlsConnection.exportKeyingMaterial(
1526 "EXPORTER-Token-Binding", "", False, 32)
1527 self.send_response(200)
1528 self.send_header('Content-Type', 'application/octet-stream')
1529 self.end_headers()
1530 self.wfile.write(ekm)
1531 return True
1532
nharpercb1adc32016-03-30 16:05:48 -07001533 def ForwardTokenBindingHeader(self):
nharpere758cd12016-07-13 17:49:36 -07001534 """Send a redirect that sets the Include-Referred-Token-Binding-ID
nharpercb1adc32016-03-30 16:05:48 -07001535 header."""
1536
1537 test_name = '/forward-tokbind'
1538 if not self._ShouldHandleRequest(test_name):
1539 return False
1540
1541 query_char = self.path.find('?')
1542 if query_char < 0 or len(self.path) <= query_char + 1:
1543 self.sendRedirectHelp(test_name)
1544 return True
1545 dest = urllib.unquote(self.path[query_char + 1:])
1546
1547 self.send_response(302)
1548 self.send_header('Location', dest)
nharpere758cd12016-07-13 17:49:36 -07001549 self.send_header('Include-Referred-Token-Binding-ID', 'true')
nharpercb1adc32016-03-30 16:05:48 -07001550 self.end_headers()
1551 return True
1552
pneubeckfd4f0442015-08-07 04:55:10 -07001553 def GetClientCert(self):
1554 """Send a reply whether a client certificate was provided."""
1555
1556 if not self._ShouldHandleRequest('/client-cert'):
1557 return False
1558
1559 self.send_response(200)
1560 self.send_header('Content-Type', 'text/plain')
1561 self.end_headers()
1562
1563 cert_chain = self.server.tlsConnection.session.clientCertChain
1564 if cert_chain != None:
1565 self.wfile.write('got client cert with fingerprint: ' +
1566 cert_chain.getFingerprint())
1567 else:
1568 self.wfile.write('got no client cert')
1569 return True
1570
davidben599e7e72014-09-03 16:19:09 -07001571 def ClientCipherListHandler(self):
1572 """Send a reply containing the cipher suite list that the client
1573 provided. Each cipher suite value is serialized in decimal, followed by a
1574 newline."""
1575
1576 if not self._ShouldHandleRequest('/client-cipher-list'):
1577 return False
1578
1579 self.send_response(200)
1580 self.send_header('Content-Type', 'text/plain')
1581 self.end_headers()
1582
davidben11682512014-10-06 21:09:11 -07001583 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1584 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001585 return True
1586
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001587 def CloseSocketHandler(self):
1588 """Closes the socket without sending anything."""
1589
1590 if not self._ShouldHandleRequest('/close-socket'):
1591 return False
1592
1593 self.wfile.close()
1594 return True
1595
initial.commit94958cf2008-07-26 22:42:52 +00001596 def DefaultResponseHandler(self):
1597 """This is the catch-all response handler for requests that aren't handled
1598 by one of the special handlers above.
1599 Note that we specify the content-length as without it the https connection
1600 is not closed properly (and the browser keeps expecting data)."""
1601
1602 contents = "Default response given for path: " + self.path
1603 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001604 self.send_header('Content-Type', 'text/html')
1605 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001606 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001607 if (self.command != 'HEAD'):
1608 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001609 return True
1610
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001611 def RedirectConnectHandler(self):
1612 """Sends a redirect to the CONNECT request for www.redirect.com. This
1613 response is not specified by the RFC, so the browser should not follow
1614 the redirect."""
1615
1616 if (self.path.find("www.redirect.com") < 0):
1617 return False
1618
1619 dest = "http://www.destination.com/foo.js"
1620
1621 self.send_response(302) # moved temporarily
1622 self.send_header('Location', dest)
1623 self.send_header('Connection', 'close')
1624 self.end_headers()
1625 return True
1626
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001627 def ServerAuthConnectHandler(self):
1628 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1629 response doesn't make sense because the proxy server cannot request
1630 server authentication."""
1631
1632 if (self.path.find("www.server-auth.com") < 0):
1633 return False
1634
1635 challenge = 'Basic realm="WallyWorld"'
1636
1637 self.send_response(401) # unauthorized
1638 self.send_header('WWW-Authenticate', challenge)
1639 self.send_header('Connection', 'close')
1640 self.end_headers()
1641 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001642
1643 def DefaultConnectResponseHandler(self):
1644 """This is the catch-all response handler for CONNECT requests that aren't
1645 handled by one of the special handlers above. Real Web servers respond
1646 with 400 to CONNECT requests."""
1647
1648 contents = "Your client has issued a malformed or illegal request."
1649 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001650 self.send_header('Content-Type', 'text/html')
1651 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001652 self.end_headers()
1653 self.wfile.write(contents)
1654 return True
1655
initial.commit94958cf2008-07-26 22:42:52 +00001656 # called by the redirect handling function when there is no parameter
1657 def sendRedirectHelp(self, redirect_name):
1658 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001659 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001660 self.end_headers()
1661 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1662 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1663 self.wfile.write('</body></html>')
1664
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001665 # called by chunked handling function
1666 def sendChunkHelp(self, chunk):
1667 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1668 self.wfile.write('%X\r\n' % len(chunk))
1669 self.wfile.write(chunk)
1670 self.wfile.write('\r\n')
1671
akalin@chromium.org154bb132010-11-12 02:20:27 +00001672
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001673class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001674 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001675 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001676 self.ocsp_response = socket_server.ocsp_response
mattm10ede842016-11-29 11:57:16 -08001677 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001678 testserver_base.BasePageHandler.__init__(self, request, client_address,
1679 socket_server, [], handlers, [],
1680 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001681
1682 def OCSPResponse(self):
mattm10ede842016-11-29 11:57:16 -08001683 if not self._ShouldHandleRequest("/ocsp"):
1684 return False
1685 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001686 self.send_response(200)
1687 self.send_header('Content-Type', 'application/ocsp-response')
1688 self.send_header('Content-Length', str(len(self.ocsp_response)))
1689 self.end_headers()
1690
1691 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001692
mattm10ede842016-11-29 11:57:16 -08001693 def CaIssuersResponse(self):
1694 if not self._ShouldHandleRequest("/ca_issuers"):
1695 return False
1696 print 'handling ca_issuers request'
1697 self.send_response(200)
1698 self.send_header('Content-Type', 'application/pkix-cert')
1699 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1700 self.end_headers()
1701
1702 self.wfile.write(self.ca_issuers_response)
1703
mattm@chromium.org830a3712012-11-07 23:00:07 +00001704
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001705class TCPEchoHandler(SocketServer.BaseRequestHandler):
1706 """The RequestHandler class for TCP echo server.
1707
1708 It is instantiated once per connection to the server, and overrides the
1709 handle() method to implement communication to the client.
1710 """
1711
1712 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001713 """Handles the request from the client and constructs a response."""
1714
1715 data = self.request.recv(65536).strip()
1716 # Verify the "echo request" message received from the client. Send back
1717 # "echo response" message if "echo request" message is valid.
1718 try:
1719 return_data = echo_message.GetEchoResponseData(data)
1720 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001721 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001722 except ValueError:
1723 return
1724
1725 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001726
1727
1728class UDPEchoHandler(SocketServer.BaseRequestHandler):
1729 """The RequestHandler class for UDP echo server.
1730
1731 It is instantiated once per connection to the server, and overrides the
1732 handle() method to implement communication to the client.
1733 """
1734
1735 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001736 """Handles the request from the client and constructs a response."""
1737
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001738 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001739 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001740 # Verify the "echo request" message received from the client. Send back
1741 # "echo response" message if "echo request" message is valid.
1742 try:
1743 return_data = echo_message.GetEchoResponseData(data)
1744 if not return_data:
1745 return
1746 except ValueError:
1747 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001748 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001749
1750
bashi@chromium.org33233532012-09-08 17:37:24 +00001751class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1752 """A request handler that behaves as a proxy server which requires
1753 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1754 """
1755
1756 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001757 redirect_connect_to_localhost = False;
bashi@chromium.org33233532012-09-08 17:37:24 +00001758
1759 def parse_request(self):
1760 """Overrides parse_request to check credential."""
1761
1762 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1763 return False
1764
1765 auth = self.headers.getheader('Proxy-Authorization')
1766 if auth != self._AUTH_CREDENTIAL:
1767 self.send_response(407)
1768 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1769 self.end_headers()
1770 return False
1771
1772 return True
1773
1774 def _start_read_write(self, sock):
1775 sock.setblocking(0)
1776 self.request.setblocking(0)
1777 rlist = [self.request, sock]
1778 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001779 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001780 if errors:
1781 self.send_response(500)
1782 self.end_headers()
1783 return
1784 for s in ready_sockets:
1785 received = s.recv(1024)
1786 if len(received) == 0:
1787 return
1788 if s == self.request:
1789 other = sock
1790 else:
1791 other = self.request
1792 other.send(received)
1793
1794 def _do_common_method(self):
1795 url = urlparse.urlparse(self.path)
1796 port = url.port
1797 if not port:
1798 if url.scheme == 'http':
1799 port = 80
1800 elif url.scheme == 'https':
1801 port = 443
1802 if not url.hostname or not port:
1803 self.send_response(400)
1804 self.end_headers()
1805 return
1806
1807 if len(url.path) == 0:
1808 path = '/'
1809 else:
1810 path = url.path
1811 if len(url.query) > 0:
1812 path = '%s?%s' % (url.path, url.query)
1813
1814 sock = None
1815 try:
1816 sock = socket.create_connection((url.hostname, port))
1817 sock.send('%s %s %s\r\n' % (
1818 self.command, path, self.protocol_version))
1819 for header in self.headers.headers:
1820 header = header.strip()
1821 if (header.lower().startswith('connection') or
1822 header.lower().startswith('proxy')):
1823 continue
1824 sock.send('%s\r\n' % header)
1825 sock.send('\r\n')
1826 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001827 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001828 self.send_response(500)
1829 self.end_headers()
1830 finally:
1831 if sock is not None:
1832 sock.close()
1833
1834 def do_CONNECT(self):
1835 try:
1836 pos = self.path.rfind(':')
1837 host = self.path[:pos]
1838 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001839 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001840 self.send_response(400)
1841 self.end_headers()
1842
Pavol Marko8ccaaed2018-01-04 18:40:04 +01001843 if BasicAuthProxyRequestHandler.redirect_connect_to_localhost:
1844 host = "127.0.0.1"
1845
bashi@chromium.org33233532012-09-08 17:37:24 +00001846 try:
1847 sock = socket.create_connection((host, port))
1848 self.send_response(200, 'Connection established')
1849 self.end_headers()
1850 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001851 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001852 self.send_response(500)
1853 self.end_headers()
1854 finally:
1855 sock.close()
1856
1857 def do_GET(self):
1858 self._do_common_method()
1859
1860 def do_HEAD(self):
1861 self._do_common_method()
1862
1863
mattm@chromium.org830a3712012-11-07 23:00:07 +00001864class ServerRunner(testserver_base.TestServerRunner):
1865 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001866
mattm@chromium.org830a3712012-11-07 23:00:07 +00001867 def __init__(self):
1868 super(ServerRunner, self).__init__()
1869 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001870
mattm@chromium.org830a3712012-11-07 23:00:07 +00001871 def __make_data_dir(self):
1872 if self.options.data_dir:
1873 if not os.path.isdir(self.options.data_dir):
1874 raise testserver_base.OptionError('specified data dir not found: ' +
1875 self.options.data_dir + ' exiting...')
1876 my_data_dir = self.options.data_dir
1877 else:
1878 # Create the default path to our data dir, relative to the exe dir.
1879 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1880 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001881
mattm@chromium.org830a3712012-11-07 23:00:07 +00001882 #TODO(ibrar): Must use Find* funtion defined in google\tools
1883 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001884
mattm@chromium.org830a3712012-11-07 23:00:07 +00001885 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001886
mattm@chromium.org830a3712012-11-07 23:00:07 +00001887 def create_server(self, server_data):
1888 port = self.options.port
1889 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001890
estark21667d62015-04-08 21:00:16 -07001891 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1892 # will result in a call to |getaddrinfo|, which fails with "nodename
1893 # nor servname provided" for localhost:0 on 10.6.
1894 if self.options.server_type == SERVER_WEBSOCKET and \
1895 host == "localhost" and \
1896 port == 0:
1897 host = "127.0.0.1"
1898
mattm@chromium.org830a3712012-11-07 23:00:07 +00001899 if self.options.server_type == SERVER_HTTP:
1900 if self.options.https:
1901 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001902 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001903 if self.options.cert_and_key_file:
1904 if not os.path.isfile(self.options.cert_and_key_file):
1905 raise testserver_base.OptionError(
1906 'specified server cert file not found: ' +
1907 self.options.cert_and_key_file + ' exiting...')
1908 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001909 elif self.options.aia_intermediate:
1910 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1911 print ('AIA server started on %s:%d...' %
1912 (host, self.__ocsp_server.server_port))
1913
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001914 ocsp_server_port = self.__ocsp_server.server_port
1915 if self.options.ocsp_proxy_port_number != 0:
1916 ocsp_server_port = self.options.ocsp_proxy_port_number
1917 server_data['ocsp_port'] = self.__ocsp_server.server_port
1918
mattm10ede842016-11-29 11:57:16 -08001919 (pem_cert_and_key, intermediate_cert_der) = \
1920 minica.GenerateCertKeyAndIntermediate(
Adam Langley1b622652017-12-05 23:57:33 +00001921 subject = self.options.cert_common_name,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001922 ca_issuers_url =
1923 ("http://%s:%d/ca_issuers" % (host, ocsp_server_port)),
mattm10ede842016-11-29 11:57:16 -08001924 serial = self.options.cert_serial)
1925
1926 self.__ocsp_server.ocsp_response = None
1927 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001928 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001929 # generate a new certificate and run an OCSP server for it.
1930 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001931 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001932 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001933
dadrian4ccf51c2016-07-20 15:36:58 -07001934 ocsp_states = list()
1935 for ocsp_state_arg in self.options.ocsp.split(':'):
1936 if ocsp_state_arg == 'ok':
1937 ocsp_state = minica.OCSP_STATE_GOOD
1938 elif ocsp_state_arg == 'revoked':
1939 ocsp_state = minica.OCSP_STATE_REVOKED
1940 elif ocsp_state_arg == 'invalid':
1941 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1942 elif ocsp_state_arg == 'unauthorized':
1943 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1944 elif ocsp_state_arg == 'unknown':
1945 ocsp_state = minica.OCSP_STATE_UNKNOWN
1946 elif ocsp_state_arg == 'later':
1947 ocsp_state = minica.OCSP_STATE_TRY_LATER
1948 elif ocsp_state_arg == 'invalid_data':
1949 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1950 elif ocsp_state_arg == "mismatched_serial":
1951 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1952 else:
1953 raise testserver_base.OptionError('unknown OCSP status: ' +
1954 ocsp_state_arg)
1955 ocsp_states.append(ocsp_state)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001956
dadrian4ccf51c2016-07-20 15:36:58 -07001957 if len(ocsp_states) > 1:
1958 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1959 raise testserver_base.OptionError('Multiple OCSP responses '
1960 'incompatible with states ' + str(ocsp_states))
1961
1962 ocsp_dates = list()
1963 for ocsp_date_arg in self.options.ocsp_date.split(':'):
1964 if ocsp_date_arg == 'valid':
1965 ocsp_date = minica.OCSP_DATE_VALID
1966 elif ocsp_date_arg == 'old':
1967 ocsp_date = minica.OCSP_DATE_OLD
1968 elif ocsp_date_arg == 'early':
1969 ocsp_date = minica.OCSP_DATE_EARLY
1970 elif ocsp_date_arg == 'long':
1971 ocsp_date = minica.OCSP_DATE_LONG
dadrian4ccf51c2016-07-20 15:36:58 -07001972 else:
1973 raise testserver_base.OptionError('unknown OCSP date: ' +
1974 ocsp_date_arg)
1975 ocsp_dates.append(ocsp_date)
1976
1977 if len(ocsp_states) != len(ocsp_dates):
1978 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1979 'count')
1980
1981 ocsp_produced = None
1982 if self.options.ocsp_produced == 'valid':
1983 ocsp_produced = minica.OCSP_PRODUCED_VALID
1984 elif self.options.ocsp_produced == 'before':
1985 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1986 elif self.options.ocsp_produced == 'after':
1987 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
mattm@chromium.org830a3712012-11-07 23:00:07 +00001988 else:
dadrian4ccf51c2016-07-20 15:36:58 -07001989 raise testserver_base.OptionError('unknown OCSP produced: ' +
1990 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001991
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001992 ocsp_server_port = self.__ocsp_server.server_port
1993 if self.options.ocsp_proxy_port_number != 0:
1994 ocsp_server_port = self.options.ocsp_proxy_port_number
1995 server_data['ocsp_port'] = self.__ocsp_server.server_port
1996
mattm@chromium.org830a3712012-11-07 23:00:07 +00001997 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
Adam Langley1b622652017-12-05 23:57:33 +00001998 subject = self.options.cert_common_name,
Sergey Ulanov475a3f22017-12-08 00:18:39 +00001999 ocsp_url = ("http://%s:%d/ocsp" % (host, ocsp_server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07002000 ocsp_states = ocsp_states,
2001 ocsp_dates = ocsp_dates,
2002 ocsp_produced = ocsp_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002003 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004
davidben3e2564a2014-11-07 18:51:00 -08002005 if self.options.ocsp_server_unavailable:
2006 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2007 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2008 else:
2009 self.__ocsp_server.ocsp_response = ocsp_der
mattm10ede842016-11-29 11:57:16 -08002010 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002011
2012 for ca_cert in self.options.ssl_client_ca:
2013 if not os.path.isfile(ca_cert):
2014 raise testserver_base.OptionError(
2015 'specified trusted client CA file not found: ' + ca_cert +
2016 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002017
2018 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002019 if self.options.staple_ocsp_response:
2020 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002021
mattm@chromium.org830a3712012-11-07 23:00:07 +00002022 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2023 self.options.ssl_client_auth,
2024 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002025 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002026 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002027 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002028 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002029 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002030 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002031 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002032 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002033 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002034 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002035 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002036 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002037 self.options.alert_after_handshake,
2038 self.options.disable_channel_id,
2039 self.options.disable_extended_master_secret,
2040 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002041 print 'HTTPS server started on https://%s:%d...' % \
2042 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002043 else:
2044 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002045 print 'HTTP server started on http://%s:%d...' % \
2046 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002047
2048 server.data_dir = self.__make_data_dir()
2049 server.file_root_url = self.options.file_root_url
2050 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002051 elif self.options.server_type == SERVER_WEBSOCKET:
2052 # Launch pywebsocket via WebSocketServer.
2053 logger = logging.getLogger()
2054 logger.addHandler(logging.StreamHandler())
2055 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2056 # is required to work correctly. It should be fixed from pywebsocket side.
2057 os.chdir(self.__make_data_dir())
2058 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002059 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002060 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002061 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002062 websocket_options.use_tls = True
Sergey Ulanovdd8b8ea2017-09-08 22:53:25 +00002063 key_path = os.path.join(ROOT_DIR, self.options.cert_and_key_file)
2064 if not os.path.isfile(key_path):
2065 raise testserver_base.OptionError(
2066 'specified server cert file not found: ' +
2067 self.options.cert_and_key_file + ' exiting...')
2068 websocket_options.private_key = key_path
2069 websocket_options.certificate = key_path
2070
mattm@chromium.org830a3712012-11-07 23:00:07 +00002071 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002072 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002073 websocket_options.tls_client_auth = True
2074 if len(self.options.ssl_client_ca) != 1:
2075 raise testserver_base.OptionError(
2076 'one trusted client CA file should be specified')
2077 if not os.path.isfile(self.options.ssl_client_ca[0]):
2078 raise testserver_base.OptionError(
2079 'specified trusted client CA file not found: ' +
2080 self.options.ssl_client_ca[0] + ' exiting...')
2081 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002082 print 'Trying to start websocket server on %s://%s:%d...' % \
2083 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002084 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002085 print 'WebSocket server started on %s://%s:%d...' % \
2086 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002087 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002088 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002089 elif self.options.server_type == SERVER_TCP_ECHO:
2090 # Used for generating the key (randomly) that encodes the "echo request"
2091 # message.
2092 random.seed()
2093 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002094 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 server_data['port'] = server.server_port
2096 elif self.options.server_type == SERVER_UDP_ECHO:
2097 # Used for generating the key (randomly) that encodes the "echo request"
2098 # message.
2099 random.seed()
2100 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002101 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002102 server_data['port'] = server.server_port
2103 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002104 BasicAuthProxyRequestHandler.redirect_connect_to_localhost = \
2105 self.options.redirect_connect_to_localhost
mattm@chromium.org830a3712012-11-07 23:00:07 +00002106 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002107 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002108 server_data['port'] = server.server_port
2109 elif self.options.server_type == SERVER_FTP:
2110 my_data_dir = self.__make_data_dir()
2111
2112 # Instantiate a dummy authorizer for managing 'virtual' users
2113 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2114
xleng9d4c45f2015-05-04 16:26:12 -07002115 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002116 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2117
xleng9d4c45f2015-05-04 16:26:12 -07002118 # Define a read-only anonymous user unless disabled
2119 if not self.options.no_anonymous_ftp_user:
2120 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002121
2122 # Instantiate FTP handler class
2123 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2124 ftp_handler.authorizer = authorizer
2125
2126 # Define a customized banner (string returned when client connects)
2127 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2128 pyftpdlib.ftpserver.__ver__)
2129
2130 # Instantiate FTP server class and listen to address:port
2131 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2132 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002133 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002134 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002135 raise testserver_base.OptionError('unknown server type' +
2136 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002137
mattm@chromium.org830a3712012-11-07 23:00:07 +00002138 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002139
mattm@chromium.org830a3712012-11-07 23:00:07 +00002140 def run_server(self):
2141 if self.__ocsp_server:
2142 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002143
mattm@chromium.org830a3712012-11-07 23:00:07 +00002144 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002145
mattm@chromium.org830a3712012-11-07 23:00:07 +00002146 if self.__ocsp_server:
2147 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002148
mattm@chromium.org830a3712012-11-07 23:00:07 +00002149 def add_options(self):
2150 testserver_base.TestServerRunner.add_options(self)
2151 self.option_parser.add_option('-f', '--ftp', action='store_const',
2152 const=SERVER_FTP, default=SERVER_HTTP,
2153 dest='server_type',
2154 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002155 self.option_parser.add_option('--tcp-echo', action='store_const',
2156 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2157 dest='server_type',
2158 help='start up a tcp echo server.')
2159 self.option_parser.add_option('--udp-echo', action='store_const',
2160 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2161 dest='server_type',
2162 help='start up a udp echo server.')
2163 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2164 const=SERVER_BASIC_AUTH_PROXY,
2165 default=SERVER_HTTP, dest='server_type',
2166 help='start up a proxy server which requires '
2167 'basic authentication.')
2168 self.option_parser.add_option('--websocket', action='store_const',
2169 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2170 dest='server_type',
2171 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002172 self.option_parser.add_option('--https', action='store_true',
2173 dest='https', help='Specify that https '
2174 'should be used.')
2175 self.option_parser.add_option('--cert-and-key-file',
2176 dest='cert_and_key_file', help='specify the '
2177 'path to the file containing the certificate '
2178 'and private key for the server in PEM '
2179 'format')
mattm10ede842016-11-29 11:57:16 -08002180 self.option_parser.add_option('--aia-intermediate', action='store_true',
2181 dest='aia_intermediate',
2182 help='generate a certificate chain that '
2183 'requires AIA cert fetching, and run a '
2184 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002185 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2186 help='The type of OCSP response generated '
2187 'for the automatically generated '
2188 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002189 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2190 default='valid', help='The validity of the '
2191 'range between thisUpdate and nextUpdate')
2192 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2193 default='valid', help='producedAt relative '
2194 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002195 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2196 default=0, type=int,
2197 help='If non-zero then the generated '
2198 'certificate will have this serial number')
Adam Langley1b622652017-12-05 23:57:33 +00002199 self.option_parser.add_option('--cert-common-name', dest='cert_common_name',
2200 default="127.0.0.1",
2201 help='The generated certificate will have '
2202 'this common name')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002203 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2204 default='0', type='int',
2205 help='If nonzero, certain TLS connections '
2206 'will be aborted in order to test version '
2207 'fallback. 1 means all TLS versions will be '
2208 'aborted. 2 means TLS 1.1 or higher will be '
2209 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002210 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002211 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002212 self.option_parser.add_option('--tls-intolerance-type',
2213 dest='tls_intolerance_type',
2214 default="alert",
2215 help='Controls how the server reacts to a '
2216 'TLS version it is intolerant to. Valid '
2217 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002218 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2219 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002220 default='',
2221 help='Base64 encoded SCT list. If set, '
2222 'server will respond with a '
2223 'signed_certificate_timestamp TLS extension '
2224 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002225 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2226 default=False, const=True,
2227 action='store_const',
2228 help='If given, TLS_FALLBACK_SCSV support '
2229 'will be enabled. This causes the server to '
2230 'reject fallback connections from compatible '
2231 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002232 self.option_parser.add_option('--staple-ocsp-response',
2233 dest='staple_ocsp_response',
2234 default=False, action='store_true',
2235 help='If set, server will staple the OCSP '
2236 'response whenever OCSP is on and the client '
2237 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002238 self.option_parser.add_option('--https-record-resume',
2239 dest='record_resume', const=True,
2240 default=False, action='store_const',
2241 help='Record resumption cache events rather '
2242 'than resuming as normal. Allows the use of '
2243 'the /ssl-session-cache request')
2244 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2245 help='Require SSL client auth on every '
2246 'connection.')
2247 self.option_parser.add_option('--ssl-client-ca', action='append',
2248 default=[], help='Specify that the client '
2249 'certificate request should include the CA '
2250 'named in the subject of the DER-encoded '
2251 'certificate contained in the specified '
2252 'file. This option may appear multiple '
2253 'times, indicating multiple CA names should '
2254 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002255 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2256 default=[], help='Specify that the client '
2257 'certificate request should include the '
2258 'specified certificate_type value. This '
2259 'option may appear multiple times, '
2260 'indicating multiple values should be send '
2261 'in the request. Valid values are '
2262 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2263 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002264 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2265 help='Specify the bulk encryption '
2266 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002267 'SSL server. Valid values are "aes128gcm", '
2268 '"aes256", "aes128", "3des", "rc4". If '
2269 'omitted, all algorithms will be used. This '
2270 'option may appear multiple times, '
2271 'indicating multiple algorithms should be '
2272 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002273 self.option_parser.add_option('--ssl-key-exchange', action='append',
2274 help='Specify the key exchange algorithm(s)'
2275 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002276 'Valid values are "rsa", "dhe_rsa", '
2277 '"ecdhe_rsa". If omitted, all algorithms '
2278 'will be used. This option may appear '
2279 'multiple times, indicating multiple '
2280 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002281 self.option_parser.add_option('--alpn-protocols', action='append',
2282 help='Specify the list of ALPN protocols. '
2283 'The server will not send an ALPN response '
2284 'if this list does not overlap with the '
2285 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002286 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002287 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002288 'an NPN response. The server will not'
2289 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002290 self.option_parser.add_option('--file-root-url', default='/files/',
2291 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002292 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2293 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2294 dest='ws_basic_auth',
2295 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002296 self.option_parser.add_option('--ocsp-server-unavailable',
2297 dest='ocsp_server_unavailable',
2298 default=False, action='store_true',
2299 help='If set, the OCSP server will return '
2300 'a tryLater status rather than the actual '
2301 'OCSP response.')
Sergey Ulanov475a3f22017-12-08 00:18:39 +00002302 self.option_parser.add_option('--ocsp-proxy-port-number', default=0,
2303 type='int', dest='ocsp_proxy_port_number',
2304 help='Port allocated for OCSP proxy '
2305 'when connection is proxied.')
davidben21cda342015-03-17 18:04:28 -07002306 self.option_parser.add_option('--alert-after-handshake',
2307 dest='alert_after_handshake',
2308 default=False, action='store_true',
2309 help='If set, the server will send a fatal '
2310 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002311 self.option_parser.add_option('--no-anonymous-ftp-user',
2312 dest='no_anonymous_ftp_user',
2313 default=False, action='store_true',
2314 help='If set, the FTP server will not create '
2315 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002316 self.option_parser.add_option('--disable-channel-id', action='store_true')
2317 self.option_parser.add_option('--disable-extended-master-secret',
2318 action='store_true')
2319 self.option_parser.add_option('--token-binding-params', action='append',
2320 default=[], type='int')
Pavol Marko8ccaaed2018-01-04 18:40:04 +01002321 self.option_parser.add_option('--redirect-connect-to-localhost',
2322 dest='redirect_connect_to_localhost',
2323 default=False, action='store_true',
2324 help='If set, the Proxy server will connect '
2325 'to localhost instead of the requested URL '
2326 'on CONNECT requests')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002327
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002328
initial.commit94958cf2008-07-26 22:42:52 +00002329if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002330 sys.exit(ServerRunner().main())