blob: c22625ea9fcc0aaddbe765e9ca800479ec67b24f [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# Temporary hack to deal with tlslite 0.3.8 -> 0.4.6 upgrade.
43#
44# TODO(davidben): Remove this when it has cycled through all the bots and
45# developer checkouts or when http://crbug.com/356276 is resolved.
46try:
47 os.remove(os.path.join(ROOT_DIR, 'third_party', 'tlslite',
48 'tlslite', 'utils', 'hmac.pyc'))
49except Exception:
50 pass
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000051
52# Append at the end of sys.path, it's fine to use the system library.
53sys.path.append(os.path.join(ROOT_DIR, 'third_party', 'pyftpdlib', 'src'))
timurrrr@chromium.orgb9006f52010-04-30 14:50:58 +000054
davidben@chromium.org7d53b542014-04-10 17:56:44 +000055# Insert at the beginning of the path, we want to use our copies of the library
maruel@chromium.org5ddc64e2013-12-05 17:50:12 +000056# unconditionally.
57sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
davidben@chromium.org7d53b542014-04-10 17:56:44 +000058sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
59
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000060import mod_pywebsocket.standalone
pliard@chromium.org3f8873f2012-11-14 11:38:55 +000061from mod_pywebsocket.standalone import WebSocketServer
yhirano@chromium.org51f90d92014-03-24 04:49:23 +000062# import manually
63mod_pywebsocket.standalone.ssl = ssl
davidben@chromium.org06fcf202010-09-22 18:15:23 +000064
davidben@chromium.org7d53b542014-04-10 17:56:44 +000065import pyftpdlib.ftpserver
66
67import tlslite
68import tlslite.api
69
70import echo_message
71import testserver_base
72
maruel@chromium.org756cf982009-03-05 12:46:38 +000073SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000074SERVER_FTP = 1
rsimha@chromium.org99a6f172013-01-20 01:10:24 +000075SERVER_TCP_ECHO = 2
76SERVER_UDP_ECHO = 3
77SERVER_BASIC_AUTH_PROXY = 4
78SERVER_WEBSOCKET = 5
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000079
80# Default request queue size for WebSocketServer.
81_DEFAULT_REQUEST_QUEUE_SIZE = 128
initial.commit94958cf2008-07-26 22:42:52 +000082
dadrian4ccf51c2016-07-20 15:36:58 -070083OCSP_STATES_NO_SINGLE_RESPONSE = {
84 minica.OCSP_STATE_INVALID_RESPONSE,
85 minica.OCSP_STATE_UNAUTHORIZED,
86 minica.OCSP_STATE_TRY_LATER,
87 minica.OCSP_STATE_INVALID_RESPONSE_DATA,
88}
89
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000090class WebSocketOptions:
91 """Holds options for WebSocketServer."""
92
93 def __init__(self, host, port, data_dir):
94 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
95 self.server_host = host
96 self.port = port
97 self.websock_handlers = data_dir
98 self.scan_dir = None
99 self.allow_handlers_outside_root_dir = False
100 self.websock_handlers_map_file = None
101 self.cgi_directories = []
102 self.is_executable_method = None
103 self.allow_draft75 = False
104 self.strict = True
105
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000106 self.use_tls = False
107 self.private_key = None
108 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +0000109 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000110 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +0000111 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000112 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000113 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000114
mattm@chromium.org830a3712012-11-07 23:00:07 +0000115
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000116class RecordingSSLSessionCache(object):
117 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
118 lookups and inserts in order to test session cache behaviours."""
119
120 def __init__(self):
121 self.log = []
122
123 def __getitem__(self, sessionID):
124 self.log.append(('lookup', sessionID))
125 raise KeyError()
126
127 def __setitem__(self, sessionID, session):
128 self.log.append(('insert', sessionID))
129
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000130
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000131class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
132 testserver_base.BrokenPipeHandlerMixIn,
133 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000134 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000135 verification."""
136
137 pass
138
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000139class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
140 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000141 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000142 """This is a specialization of HTTPServer that serves an
143 OCSP response"""
144
145 def serve_forever_on_thread(self):
146 self.thread = threading.Thread(target = self.serve_forever,
147 name = "OCSPServerThread")
148 self.thread.start()
149
150 def stop_serving(self):
151 self.shutdown()
152 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000153
mattm@chromium.org830a3712012-11-07 23:00:07 +0000154
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000155class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000156 testserver_base.ClientRestrictingServerMixIn,
157 testserver_base.BrokenPipeHandlerMixIn,
158 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000159 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000160 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000161
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000162 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000163 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc5fb33bd2016-08-05 12:09:21 -0700164 ssl_bulk_ciphers, ssl_key_exchanges, alpn_protocols,
165 npn_protocols, record_resume_info, tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000166 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700167 fallback_scsv_enabled, ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -0700168 alert_after_handshake, disable_channel_id, disable_ems,
169 token_binding_params):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000170 self.cert_chain = tlslite.api.X509CertChain()
171 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000172 # Force using only python implementation - otherwise behavior is different
173 # depending on whether m2crypto Python module is present (error is thrown
174 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
175 # the hood.
176 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
177 private=True,
178 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000179 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000180 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000181 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700182 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000183 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000184 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000185 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000186
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000187 if ssl_client_auth:
188 for ca_file in ssl_client_cas:
189 s = open(ca_file).read()
190 x509 = tlslite.api.X509()
191 x509.parse(s)
192 self.ssl_client_cas.append(x509.subject)
193
194 for cert_type in ssl_client_cert_types:
195 self.ssl_client_cert_types.append({
196 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000197 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
198 }[cert_type])
199
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000200 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800201 # Enable SSLv3 for testing purposes.
202 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000203 if ssl_bulk_ciphers is not None:
204 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000205 if ssl_key_exchanges is not None:
206 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000207 if tls_intolerant != 0:
208 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
209 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700210 if alert_after_handshake:
211 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700212 if disable_channel_id:
213 self.ssl_handshake_settings.enableChannelID = False
214 if disable_ems:
215 self.ssl_handshake_settings.enableExtendedMasterSecret = False
216 self.ssl_handshake_settings.supportedTokenBindingParams = \
217 token_binding_params
bnc5fb33bd2016-08-05 12:09:21 -0700218 self.ssl_handshake_settings.alpnProtos=alpn_protocols;
initial.commit94958cf2008-07-26 22:42:52 +0000219
rsleevi8146efa2015-03-16 12:31:24 -0700220 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000221 # If record_resume_info is true then we'll replace the session cache with
222 # an object that records the lookups and inserts that it sees.
223 self.session_cache = RecordingSSLSessionCache()
224 else:
225 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000226 testserver_base.StoppableHTTPServer.__init__(self,
227 server_address,
228 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000229
230 def handshake(self, tlsConnection):
231 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000232
initial.commit94958cf2008-07-26 22:42:52 +0000233 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000234 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000235 tlsConnection.handshakeServer(certChain=self.cert_chain,
236 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000237 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000238 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000239 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000240 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000241 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700242 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000243 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000244 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000245 fallbackSCSV=self.fallback_scsv_enabled,
246 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000247 tlsConnection.ignoreAbruptClose = True
248 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000249 except tlslite.api.TLSAbruptCloseError:
250 # Ignore abrupt close.
251 return True
initial.commit94958cf2008-07-26 22:42:52 +0000252 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000253 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000254 return False
255
akalin@chromium.org154bb132010-11-12 02:20:27 +0000256
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000257class FTPServer(testserver_base.ClientRestrictingServerMixIn,
258 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000259 """This is a specialization of FTPServer that adds client verification."""
260
261 pass
262
263
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000264class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
265 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000266 """A TCP echo server that echoes back what it has received."""
267
268 def server_bind(self):
269 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000270
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000271 SocketServer.TCPServer.server_bind(self)
272 host, port = self.socket.getsockname()[:2]
273 self.server_name = socket.getfqdn(host)
274 self.server_port = port
275
276 def serve_forever(self):
277 self.stop = False
278 self.nonce_time = None
279 while not self.stop:
280 self.handle_request()
281 self.socket.close()
282
283
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000284class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
285 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000286 """A UDP echo server that echoes back what it has received."""
287
288 def server_bind(self):
289 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000290
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000291 SocketServer.UDPServer.server_bind(self)
292 host, port = self.socket.getsockname()[:2]
293 self.server_name = socket.getfqdn(host)
294 self.server_port = port
295
296 def serve_forever(self):
297 self.stop = False
298 self.nonce_time = None
299 while not self.stop:
300 self.handle_request()
301 self.socket.close()
302
303
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000304class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000305 # Class variables to allow for persistence state between page handler
306 # invocations
307 rst_limits = {}
308 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000309
310 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000311 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000312 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000313 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000314 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000315 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000316 self.NoCacheMaxAgeTimeHandler,
317 self.NoCacheTimeHandler,
318 self.CacheTimeHandler,
319 self.CacheExpiresHandler,
320 self.CacheProxyRevalidateHandler,
321 self.CachePrivateHandler,
322 self.CachePublicHandler,
323 self.CacheSMaxAgeHandler,
324 self.CacheMustRevalidateHandler,
325 self.CacheMustRevalidateMaxAgeHandler,
326 self.CacheNoStoreHandler,
327 self.CacheNoStoreMaxAgeHandler,
328 self.CacheNoTransformHandler,
329 self.DownloadHandler,
330 self.DownloadFinishHandler,
331 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000332 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000333 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000334 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000336 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000337 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000338 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000339 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.AuthBasicHandler,
341 self.AuthDigestHandler,
342 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000343 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000344 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000345 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700346 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000347 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000348 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000349 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000350 self.GetChannelID,
nharper08eae822016-01-25 15:54:14 -0800351 self.GetTokenBindingEKM,
nharpercb1adc32016-03-30 16:05:48 -0700352 self.ForwardTokenBindingHeader,
pneubeckfd4f0442015-08-07 04:55:10 -0700353 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700354 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000355 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000356 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000357 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000358 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000359 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000360 self.PostOnlyFileHandler,
361 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000362 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000363 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000364 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000365 head_handlers = [
366 self.FileHandler,
367 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000368
maruel@google.come250a9b2009-03-10 17:39:46 +0000369 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000370 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000371 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000372 'gif': 'image/gif',
373 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000374 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700375 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000376 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000377 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000378 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000379 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000380 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000381 }
initial.commit94958cf2008-07-26 22:42:52 +0000382 self._default_mime_type = 'text/html'
383
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000384 testserver_base.BasePageHandler.__init__(self, request, client_address,
385 socket_server, connect_handlers,
386 get_handlers, head_handlers,
387 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000388
initial.commit94958cf2008-07-26 22:42:52 +0000389 def GetMIMETypeFromName(self, file_name):
390 """Returns the mime type for the specified file_name. So far it only looks
391 at the file extension."""
392
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000393 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000394 if len(extension) == 0:
395 # no extension.
396 return self._default_mime_type
397
ericroman@google.comc17ca532009-05-07 03:51:05 +0000398 # extension starts with a dot, so we need to remove it
399 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000400
initial.commit94958cf2008-07-26 22:42:52 +0000401 def NoCacheMaxAgeTimeHandler(self):
402 """This request handler yields a page with the title set to the current
403 system time, and no caching requested."""
404
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000405 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000406 return False
407
408 self.send_response(200)
409 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000410 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000411 self.end_headers()
412
maruel@google.come250a9b2009-03-10 17:39:46 +0000413 self.wfile.write('<html><head><title>%s</title></head></html>' %
414 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000415
416 return True
417
418 def NoCacheTimeHandler(self):
419 """This request handler yields a page with the title set to the current
420 system time, and no caching requested."""
421
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000422 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000423 return False
424
425 self.send_response(200)
426 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000427 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000428 self.end_headers()
429
maruel@google.come250a9b2009-03-10 17:39:46 +0000430 self.wfile.write('<html><head><title>%s</title></head></html>' %
431 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000432
433 return True
434
435 def CacheTimeHandler(self):
436 """This request handler yields a page with the title set to the current
437 system time, and allows caching for one minute."""
438
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000439 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000440 return False
441
442 self.send_response(200)
443 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000444 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000445 self.end_headers()
446
maruel@google.come250a9b2009-03-10 17:39:46 +0000447 self.wfile.write('<html><head><title>%s</title></head></html>' %
448 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000449
450 return True
451
452 def CacheExpiresHandler(self):
453 """This request handler yields a page with the title set to the current
454 system time, and set the page to expire on 1 Jan 2099."""
455
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000456 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000457 return False
458
459 self.send_response(200)
460 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000461 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000462 self.end_headers()
463
maruel@google.come250a9b2009-03-10 17:39:46 +0000464 self.wfile.write('<html><head><title>%s</title></head></html>' %
465 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000466
467 return True
468
469 def CacheProxyRevalidateHandler(self):
470 """This request handler yields a page with the title set to the current
471 system time, and allows caching for 60 seconds"""
472
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000473 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000474 return False
475
476 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000477 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000478 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
479 self.end_headers()
480
maruel@google.come250a9b2009-03-10 17:39:46 +0000481 self.wfile.write('<html><head><title>%s</title></head></html>' %
482 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000483
484 return True
485
486 def CachePrivateHandler(self):
487 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700488 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000489
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000490 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000491 return False
492
493 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000494 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000495 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000496 self.end_headers()
497
maruel@google.come250a9b2009-03-10 17:39:46 +0000498 self.wfile.write('<html><head><title>%s</title></head></html>' %
499 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000500
501 return True
502
503 def CachePublicHandler(self):
504 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700505 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000506
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000507 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000508 return False
509
510 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000511 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000512 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000513 self.end_headers()
514
maruel@google.come250a9b2009-03-10 17:39:46 +0000515 self.wfile.write('<html><head><title>%s</title></head></html>' %
516 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000517
518 return True
519
520 def CacheSMaxAgeHandler(self):
521 """This request handler yields a page with the title set to the current
522 system time, and does not allow for caching."""
523
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000524 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000525 return False
526
527 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000528 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000529 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
530 self.end_headers()
531
maruel@google.come250a9b2009-03-10 17:39:46 +0000532 self.wfile.write('<html><head><title>%s</title></head></html>' %
533 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000534
535 return True
536
537 def CacheMustRevalidateHandler(self):
538 """This request handler yields a page with the title set to the current
539 system time, and does not allow caching."""
540
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000541 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000542 return False
543
544 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000545 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000546 self.send_header('Cache-Control', 'must-revalidate')
547 self.end_headers()
548
maruel@google.come250a9b2009-03-10 17:39:46 +0000549 self.wfile.write('<html><head><title>%s</title></head></html>' %
550 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000551
552 return True
553
554 def CacheMustRevalidateMaxAgeHandler(self):
555 """This request handler yields a page with the title set to the current
556 system time, and does not allow caching event though max-age of 60
557 seconds is specified."""
558
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000559 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000560 return False
561
562 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000563 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000564 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
565 self.end_headers()
566
maruel@google.come250a9b2009-03-10 17:39:46 +0000567 self.wfile.write('<html><head><title>%s</title></head></html>' %
568 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000569
570 return True
571
initial.commit94958cf2008-07-26 22:42:52 +0000572 def CacheNoStoreHandler(self):
573 """This request handler yields a page with the title set to the current
574 system time, and does not allow the page to be stored."""
575
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000576 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000577 return False
578
579 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000580 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000581 self.send_header('Cache-Control', 'no-store')
582 self.end_headers()
583
maruel@google.come250a9b2009-03-10 17:39:46 +0000584 self.wfile.write('<html><head><title>%s</title></head></html>' %
585 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000586
587 return True
588
589 def CacheNoStoreMaxAgeHandler(self):
590 """This request handler yields a page with the title set to the current
591 system time, and does not allow the page to be stored even though max-age
592 of 60 seconds is specified."""
593
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000594 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000595 return False
596
597 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000598 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000599 self.send_header('Cache-Control', 'max-age=60, no-store')
600 self.end_headers()
601
maruel@google.come250a9b2009-03-10 17:39:46 +0000602 self.wfile.write('<html><head><title>%s</title></head></html>' %
603 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000604
605 return True
606
607
608 def CacheNoTransformHandler(self):
609 """This request handler yields a page with the title set to the current
610 system time, and does not allow the content to transformed during
611 user-agent caching"""
612
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000613 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000614 return False
615
616 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000617 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000618 self.send_header('Cache-Control', 'no-transform')
619 self.end_headers()
620
maruel@google.come250a9b2009-03-10 17:39:46 +0000621 self.wfile.write('<html><head><title>%s</title></head></html>' %
622 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000623
624 return True
625
626 def EchoHeader(self):
627 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000628
ananta@chromium.org219b2062009-10-23 16:09:41 +0000629 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000630
ananta@chromium.org56812d02011-04-07 17:52:05 +0000631 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000632 """This function echoes back the value of a specific request header while
633 allowing caching for 16 hours."""
634
ananta@chromium.org56812d02011-04-07 17:52:05 +0000635 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000636
637 def EchoHeaderHelper(self, echo_header):
638 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000639
ananta@chromium.org219b2062009-10-23 16:09:41 +0000640 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000641 return False
642
643 query_char = self.path.find('?')
644 if query_char != -1:
645 header_name = self.path[query_char+1:]
646
647 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000648 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000649 if echo_header == '/echoheadercache':
650 self.send_header('Cache-control', 'max-age=60000')
651 else:
652 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000653 # insert a vary header to properly indicate that the cachability of this
654 # request is subject to value of the request header being echoed.
655 if len(header_name) > 0:
656 self.send_header('Vary', header_name)
657 self.end_headers()
658
659 if len(header_name) > 0:
660 self.wfile.write(self.headers.getheader(header_name))
661
662 return True
663
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000664 def ReadRequestBody(self):
665 """This function reads the body of the current HTTP request, handling
666 both plain and chunked transfer encoded requests."""
667
668 if self.headers.getheader('transfer-encoding') != 'chunked':
669 length = int(self.headers.getheader('content-length'))
670 return self.rfile.read(length)
671
672 # Read the request body as chunks.
673 body = ""
674 while True:
675 line = self.rfile.readline()
676 length = int(line, 16)
677 if length == 0:
678 self.rfile.readline()
679 break
680 body += self.rfile.read(length)
681 self.rfile.read(2)
682 return body
683
initial.commit94958cf2008-07-26 22:42:52 +0000684 def EchoHandler(self):
685 """This handler just echoes back the payload of the request, for testing
686 form submission."""
687
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000688 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000689 return False
690
hirono2838c572015-01-21 12:18:11 -0800691 _, _, _, _, query, _ = urlparse.urlparse(self.path)
692 query_params = cgi.parse_qs(query, True)
693 if 'status' in query_params:
694 self.send_response(int(query_params['status'][0]))
695 else:
696 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000697 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000698 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000699 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000700 return True
701
702 def EchoTitleHandler(self):
703 """This handler is like Echo, but sets the page title to the request."""
704
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000705 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000706 return False
707
708 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000709 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000710 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000711 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000712 self.wfile.write('<html><head><title>')
713 self.wfile.write(request)
714 self.wfile.write('</title></head></html>')
715 return True
716
717 def EchoAllHandler(self):
718 """This handler yields a (more) human-readable page listing information
719 about the request header & contents."""
720
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000721 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000722 return False
723
724 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000725 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000726 self.end_headers()
727 self.wfile.write('<html><head><style>'
728 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
729 '</style></head><body>'
730 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000731 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000732 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000733
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000734 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000735 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000736 params = cgi.parse_qs(qs, keep_blank_values=1)
737
738 for param in params:
739 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000740
741 self.wfile.write('</pre>')
742
743 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
744
745 self.wfile.write('</body></html>')
746 return True
747
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000748 def EchoMultipartPostHandler(self):
749 """This handler echoes received multipart post data as json format."""
750
751 if not (self._ShouldHandleRequest("/echomultipartpost") or
752 self._ShouldHandleRequest("/searchbyimage")):
753 return False
754
755 content_type, parameters = cgi.parse_header(
756 self.headers.getheader('content-type'))
757 if content_type == 'multipart/form-data':
758 post_multipart = cgi.parse_multipart(self.rfile, parameters)
759 elif content_type == 'application/x-www-form-urlencoded':
760 raise Exception('POST by application/x-www-form-urlencoded is '
761 'not implemented.')
762 else:
763 post_multipart = {}
764
765 # Since the data can be binary, we encode them by base64.
766 post_multipart_base64_encoded = {}
767 for field, values in post_multipart.items():
768 post_multipart_base64_encoded[field] = [base64.b64encode(value)
769 for value in values]
770
771 result = {'POST_multipart' : post_multipart_base64_encoded}
772
773 self.send_response(200)
774 self.send_header("Content-type", "text/plain")
775 self.end_headers()
776 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
777 return True
778
initial.commit94958cf2008-07-26 22:42:52 +0000779 def DownloadHandler(self):
780 """This handler sends a downloadable file with or without reporting
781 the size (6K)."""
782
783 if self.path.startswith("/download-unknown-size"):
784 send_length = False
785 elif self.path.startswith("/download-known-size"):
786 send_length = True
787 else:
788 return False
789
790 #
791 # The test which uses this functionality is attempting to send
792 # small chunks of data to the client. Use a fairly large buffer
793 # so that we'll fill chrome's IO buffer enough to force it to
794 # actually write the data.
795 # See also the comments in the client-side of this test in
796 # download_uitest.cc
797 #
798 size_chunk1 = 35*1024
799 size_chunk2 = 10*1024
800
801 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000802 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000803 self.send_header('Cache-Control', 'max-age=0')
804 if send_length:
805 self.send_header('Content-Length', size_chunk1 + size_chunk2)
806 self.end_headers()
807
808 # First chunk of data:
809 self.wfile.write("*" * size_chunk1)
810 self.wfile.flush()
811
812 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000813 self.server.wait_for_download = True
814 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000815 self.server.handle_request()
816
817 # Second chunk of data:
818 self.wfile.write("*" * size_chunk2)
819 return True
820
821 def DownloadFinishHandler(self):
822 """This handler just tells the server to finish the current download."""
823
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000824 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000825 return False
826
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000827 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000828 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000829 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000830 self.send_header('Cache-Control', 'max-age=0')
831 self.end_headers()
832 return True
833
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000834 def _ReplaceFileData(self, data, query_parameters):
835 """Replaces matching substrings in a file.
836
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000837 If the 'replace_text' URL query parameter is present, it is expected to be
838 of the form old_text:new_text, which indicates that any old_text strings in
839 the file are replaced with new_text. Multiple 'replace_text' parameters may
840 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000841
842 If the parameters are not present, |data| is returned.
843 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000844
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000845 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000846 replace_text_values = query_dict.get('replace_text', [])
847 for replace_text_value in replace_text_values:
848 replace_text_args = replace_text_value.split(':')
849 if len(replace_text_args) != 2:
850 raise ValueError(
851 'replace_text must be of form old_text:new_text. Actual value: %s' %
852 replace_text_value)
853 old_text_b64, new_text_b64 = replace_text_args
854 old_text = base64.urlsafe_b64decode(old_text_b64)
855 new_text = base64.urlsafe_b64decode(new_text_b64)
856 data = data.replace(old_text, new_text)
857 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000858
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000859 def ZipFileHandler(self):
860 """This handler sends the contents of the requested file in compressed form.
861 Can pass in a parameter that specifies that the content length be
862 C - the compressed size (OK),
863 U - the uncompressed size (Non-standard, but handled),
864 S - less than compressed (OK because we keep going),
865 M - larger than compressed but less than uncompressed (an error),
866 L - larger than uncompressed (an error)
867 Example: compressedfiles/Picture_1.doc?C
868 """
869
870 prefix = "/compressedfiles/"
871 if not self.path.startswith(prefix):
872 return False
873
874 # Consume a request body if present.
875 if self.command == 'POST' or self.command == 'PUT' :
876 self.ReadRequestBody()
877
878 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
879
880 if not query in ('C', 'U', 'S', 'M', 'L'):
881 return False
882
883 sub_path = url_path[len(prefix):]
884 entries = sub_path.split('/')
885 file_path = os.path.join(self.server.data_dir, *entries)
886 if os.path.isdir(file_path):
887 file_path = os.path.join(file_path, 'index.html')
888
889 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000890 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000891 self.send_error(404)
892 return True
893
894 f = open(file_path, "rb")
895 data = f.read()
896 uncompressed_len = len(data)
897 f.close()
898
899 # Compress the data.
900 data = zlib.compress(data)
901 compressed_len = len(data)
902
903 content_length = compressed_len
904 if query == 'U':
905 content_length = uncompressed_len
906 elif query == 'S':
907 content_length = compressed_len / 2
908 elif query == 'M':
909 content_length = (compressed_len + uncompressed_len) / 2
910 elif query == 'L':
911 content_length = compressed_len + uncompressed_len
912
913 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000914 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000915 self.send_header('Content-encoding', 'deflate')
916 self.send_header('Connection', 'close')
917 self.send_header('Content-Length', content_length)
918 self.send_header('ETag', '\'' + file_path + '\'')
919 self.end_headers()
920
921 self.wfile.write(data)
922
923 return True
924
initial.commit94958cf2008-07-26 22:42:52 +0000925 def FileHandler(self):
926 """This handler sends the contents of the requested file. Wow, it's like
927 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000928
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000929 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000930 if not self.path.startswith(prefix):
931 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000932 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000933
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000934 def PostOnlyFileHandler(self):
935 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000936
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000937 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000938 if not self.path.startswith(prefix):
939 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000940 return self._FileHandlerHelper(prefix)
941
942 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000943 request_body = ''
944 if self.command == 'POST' or self.command == 'PUT':
945 # Consume a request body if present.
946 request_body = self.ReadRequestBody()
947
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000948 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000949 query_dict = cgi.parse_qs(query)
950
951 expected_body = query_dict.get('expected_body', [])
952 if expected_body and request_body not in expected_body:
953 self.send_response(404)
954 self.end_headers()
955 self.wfile.write('')
956 return True
957
958 expected_headers = query_dict.get('expected_headers', [])
959 for expected_header in expected_headers:
960 header_name, expected_value = expected_header.split(':')
961 if self.headers.getheader(header_name) != expected_value:
962 self.send_response(404)
963 self.end_headers()
964 self.wfile.write('')
965 return True
966
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000967 sub_path = url_path[len(prefix):]
968 entries = sub_path.split('/')
969 file_path = os.path.join(self.server.data_dir, *entries)
970 if os.path.isdir(file_path):
971 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000972
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000973 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000974 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000975 self.send_error(404)
976 return True
977
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000978 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000979 data = f.read()
980 f.close()
981
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000982 data = self._ReplaceFileData(data, query)
983
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000984 old_protocol_version = self.protocol_version
985
initial.commit94958cf2008-07-26 22:42:52 +0000986 # If file.mock-http-headers exists, it contains the headers we
987 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000988 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000989 if os.path.isfile(headers_path):
990 f = open(headers_path, "r")
991
992 # "HTTP/1.1 200 OK"
993 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000994 http_major, http_minor, status_code = re.findall(
995 'HTTP/(\d+).(\d+) (\d+)', response)[0]
996 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000997 self.send_response(int(status_code))
998
999 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +00001000 header_values = re.findall('(\S+):\s*(.*)', line)
1001 if len(header_values) > 0:
1002 # "name: value"
1003 name, value = header_values[0]
1004 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +00001005 f.close()
1006 else:
1007 # Could be more generic once we support mime-type sniffing, but for
1008 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001009
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001010 range_header = self.headers.get('Range')
1011 if range_header and range_header.startswith('bytes='):
1012 # Note this doesn't handle all valid byte range_header values (i.e.
1013 # left open ended ones), just enough for what we needed so far.
1014 range_header = range_header[6:].split('-')
1015 start = int(range_header[0])
1016 if range_header[1]:
1017 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001018 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001019 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001020
1021 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001022 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1023 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001024 self.send_header('Content-Range', content_range)
1025 data = data[start: end + 1]
1026 else:
1027 self.send_response(200)
1028
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001029 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001030 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001031 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001032 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001033 self.end_headers()
1034
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001035 if (self.command != 'HEAD'):
1036 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001037
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001038 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001039 return True
1040
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001041 def SetCookieHandler(self):
1042 """This handler just sets a cookie, for testing cookie handling."""
1043
1044 if not self._ShouldHandleRequest("/set-cookie"):
1045 return False
1046
1047 query_char = self.path.find('?')
1048 if query_char != -1:
1049 cookie_values = self.path[query_char + 1:].split('&')
1050 else:
1051 cookie_values = ("",)
1052 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001053 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001054 for cookie_value in cookie_values:
1055 self.send_header('Set-Cookie', '%s' % cookie_value)
1056 self.end_headers()
1057 for cookie_value in cookie_values:
1058 self.wfile.write('%s' % cookie_value)
1059 return True
1060
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001061 def SetManyCookiesHandler(self):
1062 """This handler just sets a given number of cookies, for testing handling
1063 of large numbers of cookies."""
1064
1065 if not self._ShouldHandleRequest("/set-many-cookies"):
1066 return False
1067
1068 query_char = self.path.find('?')
1069 if query_char != -1:
1070 num_cookies = int(self.path[query_char + 1:])
1071 else:
1072 num_cookies = 0
1073 self.send_response(200)
1074 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001075 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001076 self.send_header('Set-Cookie', 'a=')
1077 self.end_headers()
1078 self.wfile.write('%d cookies were sent' % num_cookies)
1079 return True
1080
mattm@chromium.org983fc462012-06-30 00:52:08 +00001081 def ExpectAndSetCookieHandler(self):
1082 """Expects some cookies to be sent, and if they are, sets more cookies.
1083
1084 The expect parameter specifies a required cookie. May be specified multiple
1085 times.
1086 The set parameter specifies a cookie to set if all required cookies are
1087 preset. May be specified multiple times.
1088 The data parameter specifies the response body data to be returned."""
1089
1090 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1091 return False
1092
1093 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1094 query_dict = cgi.parse_qs(query)
1095 cookies = set()
1096 if 'Cookie' in self.headers:
1097 cookie_header = self.headers.getheader('Cookie')
1098 cookies.update([s.strip() for s in cookie_header.split(';')])
1099 got_all_expected_cookies = True
1100 for expected_cookie in query_dict.get('expect', []):
1101 if expected_cookie not in cookies:
1102 got_all_expected_cookies = False
1103 self.send_response(200)
1104 self.send_header('Content-Type', 'text/html')
1105 if got_all_expected_cookies:
1106 for cookie_value in query_dict.get('set', []):
1107 self.send_header('Set-Cookie', '%s' % cookie_value)
1108 self.end_headers()
1109 for data_value in query_dict.get('data', []):
1110 self.wfile.write(data_value)
1111 return True
1112
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001113 def SetHeaderHandler(self):
1114 """This handler sets a response header. Parameters are in the
1115 key%3A%20value&key2%3A%20value2 format."""
1116
1117 if not self._ShouldHandleRequest("/set-header"):
1118 return False
1119
1120 query_char = self.path.find('?')
1121 if query_char != -1:
1122 headers_values = self.path[query_char + 1:].split('&')
1123 else:
1124 headers_values = ("",)
1125 self.send_response(200)
1126 self.send_header('Content-Type', 'text/html')
1127 for header_value in headers_values:
1128 header_value = urllib.unquote(header_value)
1129 (key, value) = header_value.split(': ', 1)
1130 self.send_header(key, value)
1131 self.end_headers()
1132 for header_value in headers_values:
1133 self.wfile.write('%s' % header_value)
1134 return True
1135
initial.commit94958cf2008-07-26 22:42:52 +00001136 def AuthBasicHandler(self):
1137 """This handler tests 'Basic' authentication. It just sends a page with
1138 title 'user/pass' if you succeed."""
1139
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001140 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001141 return False
1142
1143 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001144 expected_password = 'secret'
1145 realm = 'testrealm'
1146 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001147
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001148 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1149 query_params = cgi.parse_qs(query, True)
1150 if 'set-cookie-if-challenged' in query_params:
1151 set_cookie_if_challenged = True
1152 if 'password' in query_params:
1153 expected_password = query_params['password'][0]
1154 if 'realm' in query_params:
1155 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001156
initial.commit94958cf2008-07-26 22:42:52 +00001157 auth = self.headers.getheader('authorization')
1158 try:
1159 if not auth:
1160 raise Exception('no auth')
1161 b64str = re.findall(r'Basic (\S+)', auth)[0]
1162 userpass = base64.b64decode(b64str)
1163 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001164 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001165 raise Exception('wrong password')
1166 except Exception, e:
1167 # Authentication failed.
1168 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001169 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001170 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001171 if set_cookie_if_challenged:
1172 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001173 self.end_headers()
1174 self.wfile.write('<html><head>')
1175 self.wfile.write('<title>Denied: %s</title>' % e)
1176 self.wfile.write('</head><body>')
1177 self.wfile.write('auth=%s<p>' % auth)
1178 self.wfile.write('b64str=%s<p>' % b64str)
1179 self.wfile.write('username: %s<p>' % username)
1180 self.wfile.write('userpass: %s<p>' % userpass)
1181 self.wfile.write('password: %s<p>' % password)
1182 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1183 self.wfile.write('</body></html>')
1184 return True
1185
1186 # Authentication successful. (Return a cachable response to allow for
1187 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001188 old_protocol_version = self.protocol_version
1189 self.protocol_version = "HTTP/1.1"
1190
initial.commit94958cf2008-07-26 22:42:52 +00001191 if_none_match = self.headers.getheader('if-none-match')
1192 if if_none_match == "abc":
1193 self.send_response(304)
1194 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001195 elif url_path.endswith(".gif"):
1196 # Using chrome/test/data/google/logo.gif as the test image
1197 test_image_path = ['google', 'logo.gif']
1198 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1199 if not os.path.isfile(gif_path):
1200 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001201 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001202 return True
1203
1204 f = open(gif_path, "rb")
1205 data = f.read()
1206 f.close()
1207
1208 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001209 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001210 self.send_header('Cache-control', 'max-age=60000')
1211 self.send_header('Etag', 'abc')
1212 self.end_headers()
1213 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001214 else:
1215 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001216 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001217 self.send_header('Cache-control', 'max-age=60000')
1218 self.send_header('Etag', 'abc')
1219 self.end_headers()
1220 self.wfile.write('<html><head>')
1221 self.wfile.write('<title>%s/%s</title>' % (username, password))
1222 self.wfile.write('</head><body>')
1223 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001224 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001225 self.wfile.write('</body></html>')
1226
rvargas@google.com54453b72011-05-19 01:11:11 +00001227 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001228 return True
1229
tonyg@chromium.org75054202010-03-31 22:06:10 +00001230 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001231 """Returns a nonce that's stable per request path for the server's lifetime.
1232 This is a fake implementation. A real implementation would only use a given
1233 nonce a single time (hence the name n-once). However, for the purposes of
1234 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001235
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001236 Args:
1237 force_reset: Iff set, the nonce will be changed. Useful for testing the
1238 "stale" response.
1239 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001240
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001241 if force_reset or not self.server.nonce_time:
1242 self.server.nonce_time = time.time()
1243 return hashlib.md5('privatekey%s%d' %
1244 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001245
1246 def AuthDigestHandler(self):
1247 """This handler tests 'Digest' authentication.
1248
1249 It just sends a page with title 'user/pass' if you succeed.
1250
1251 A stale response is sent iff "stale" is present in the request path.
1252 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001253
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001254 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001255 return False
1256
tonyg@chromium.org75054202010-03-31 22:06:10 +00001257 stale = 'stale' in self.path
1258 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001259 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001260 password = 'secret'
1261 realm = 'testrealm'
1262
1263 auth = self.headers.getheader('authorization')
1264 pairs = {}
1265 try:
1266 if not auth:
1267 raise Exception('no auth')
1268 if not auth.startswith('Digest'):
1269 raise Exception('not digest')
1270 # Pull out all the name="value" pairs as a dictionary.
1271 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1272
1273 # Make sure it's all valid.
1274 if pairs['nonce'] != nonce:
1275 raise Exception('wrong nonce')
1276 if pairs['opaque'] != opaque:
1277 raise Exception('wrong opaque')
1278
1279 # Check the 'response' value and make sure it matches our magic hash.
1280 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001281 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001282 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001283 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001284 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001285 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001286 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1287 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001288 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001289
1290 if pairs['response'] != response:
1291 raise Exception('wrong password')
1292 except Exception, e:
1293 # Authentication failed.
1294 self.send_response(401)
1295 hdr = ('Digest '
1296 'realm="%s", '
1297 'domain="/", '
1298 'qop="auth", '
1299 'algorithm=MD5, '
1300 'nonce="%s", '
1301 'opaque="%s"') % (realm, nonce, opaque)
1302 if stale:
1303 hdr += ', stale="TRUE"'
1304 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001305 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001306 self.end_headers()
1307 self.wfile.write('<html><head>')
1308 self.wfile.write('<title>Denied: %s</title>' % e)
1309 self.wfile.write('</head><body>')
1310 self.wfile.write('auth=%s<p>' % auth)
1311 self.wfile.write('pairs=%s<p>' % pairs)
1312 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1313 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1314 self.wfile.write('</body></html>')
1315 return True
1316
1317 # Authentication successful.
1318 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001319 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001320 self.end_headers()
1321 self.wfile.write('<html><head>')
1322 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1323 self.wfile.write('</head><body>')
1324 self.wfile.write('auth=%s<p>' % auth)
1325 self.wfile.write('pairs=%s<p>' % pairs)
1326 self.wfile.write('</body></html>')
1327
1328 return True
1329
1330 def SlowServerHandler(self):
1331 """Wait for the user suggested time before responding. The syntax is
1332 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001333
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001334 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001335 return False
1336 query_char = self.path.find('?')
1337 wait_sec = 1.0
1338 if query_char >= 0:
1339 try:
davidben05f82202015-03-31 13:48:07 -07001340 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001341 except ValueError:
1342 pass
1343 time.sleep(wait_sec)
1344 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001345 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001346 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001347 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001348 return True
1349
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001350 def ChunkedServerHandler(self):
1351 """Send chunked response. Allows to specify chunks parameters:
1352 - waitBeforeHeaders - ms to wait before sending headers
1353 - waitBetweenChunks - ms to wait between chunks
1354 - chunkSize - size of each chunk in bytes
1355 - chunksNumber - number of chunks
1356 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1357 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001358
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001359 if not self._ShouldHandleRequest("/chunked"):
1360 return False
1361 query_char = self.path.find('?')
1362 chunkedSettings = {'waitBeforeHeaders' : 0,
1363 'waitBetweenChunks' : 0,
1364 'chunkSize' : 5,
1365 'chunksNumber' : 5}
1366 if query_char >= 0:
1367 params = self.path[query_char + 1:].split('&')
1368 for param in params:
1369 keyValue = param.split('=')
1370 if len(keyValue) == 2:
1371 try:
1372 chunkedSettings[keyValue[0]] = int(keyValue[1])
1373 except ValueError:
1374 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001375 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001376 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1377 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001378 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001379 self.send_header('Connection', 'close')
1380 self.send_header('Transfer-Encoding', 'chunked')
1381 self.end_headers()
1382 # Chunked encoding: sending all chunks, then final zero-length chunk and
1383 # then final CRLF.
1384 for i in range(0, chunkedSettings['chunksNumber']):
1385 if i > 0:
1386 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1387 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001388 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001389 self.sendChunkHelp('')
1390 return True
1391
creis@google.com2f4f6a42011-03-25 19:44:19 +00001392 def NoContentHandler(self):
1393 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001394
creis@google.com2f4f6a42011-03-25 19:44:19 +00001395 if not self._ShouldHandleRequest("/nocontent"):
1396 return False
1397 self.send_response(204)
1398 self.end_headers()
1399 return True
1400
initial.commit94958cf2008-07-26 22:42:52 +00001401 def ServerRedirectHandler(self):
1402 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001403 '/server-redirect?http://foo.bar/asdf' to redirect to
1404 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001405
1406 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001407 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001408 return False
1409
1410 query_char = self.path.find('?')
1411 if query_char < 0 or len(self.path) <= query_char + 1:
1412 self.sendRedirectHelp(test_name)
1413 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001414 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001415
1416 self.send_response(301) # moved permanently
1417 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001418 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001419 self.end_headers()
1420 self.wfile.write('<html><head>')
1421 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1422
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001423 return True
initial.commit94958cf2008-07-26 22:42:52 +00001424
naskoe7a0d0d2014-09-29 08:53:05 -07001425 def CrossSiteRedirectHandler(self):
1426 """Sends a server redirect to the given site. The syntax is
1427 '/cross-site/hostname/...' to redirect to //hostname/...
1428 It is used to navigate between different Sites, causing
1429 cross-site/cross-process navigations in the browser."""
1430
1431 test_name = "/cross-site"
1432 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001433 return False
1434
1435 params = urllib.unquote(self.path[(len(test_name) + 1):])
1436 slash = params.find('/')
1437 if slash < 0:
1438 self.sendRedirectHelp(test_name)
1439 return True
1440
1441 host = params[:slash]
1442 path = params[(slash+1):]
1443 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1444
1445 self.send_response(301) # moved permanently
1446 self.send_header('Location', dest)
1447 self.send_header('Content-Type', 'text/html')
1448 self.end_headers()
1449 self.wfile.write('<html><head>')
1450 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1451
1452 return True
1453
initial.commit94958cf2008-07-26 22:42:52 +00001454 def ClientRedirectHandler(self):
1455 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001456 '/client-redirect?http://foo.bar/asdf' to redirect to
1457 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001458
1459 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001460 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001461 return False
1462
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001463 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001464 if query_char < 0 or len(self.path) <= query_char + 1:
1465 self.sendRedirectHelp(test_name)
1466 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001467 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001468
1469 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001470 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001471 self.end_headers()
1472 self.wfile.write('<html><head>')
1473 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1474 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1475
1476 return True
1477
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001478 def GetSSLSessionCacheHandler(self):
1479 """Send a reply containing a log of the session cache operations."""
1480
1481 if not self._ShouldHandleRequest('/ssl-session-cache'):
1482 return False
1483
1484 self.send_response(200)
1485 self.send_header('Content-Type', 'text/plain')
1486 self.end_headers()
1487 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001488 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001489 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001490 self.wfile.write('Pass --https-record-resume in order to use' +
1491 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001492 return True
1493
1494 for (action, sessionID) in log:
1495 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001496 return True
1497
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001498 def SSLManySmallRecords(self):
1499 """Sends a reply consisting of a variety of small writes. These will be
1500 translated into a series of small SSL records when used over an HTTPS
1501 server."""
1502
1503 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1504 return False
1505
1506 self.send_response(200)
1507 self.send_header('Content-Type', 'text/plain')
1508 self.end_headers()
1509
1510 # Write ~26K of data, in 1350 byte chunks
1511 for i in xrange(20):
1512 self.wfile.write('*' * 1350)
1513 self.wfile.flush()
1514 return True
1515
agl@chromium.org04700be2013-03-02 18:40:41 +00001516 def GetChannelID(self):
1517 """Send a reply containing the hashed ChannelID that the client provided."""
1518
1519 if not self._ShouldHandleRequest('/channel-id'):
1520 return False
1521
1522 self.send_response(200)
1523 self.send_header('Content-Type', 'text/plain')
1524 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001525 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001526 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1527 return True
1528
nharper08eae822016-01-25 15:54:14 -08001529 def GetTokenBindingEKM(self):
1530 """Send a reply containing the EKM value for token binding from the TLS
1531 layer."""
1532
1533 if not self._ShouldHandleRequest('/tokbind-ekm'):
1534 return False
1535
1536 ekm = self.server.tlsConnection.exportKeyingMaterial(
1537 "EXPORTER-Token-Binding", "", False, 32)
1538 self.send_response(200)
1539 self.send_header('Content-Type', 'application/octet-stream')
1540 self.end_headers()
1541 self.wfile.write(ekm)
1542 return True
1543
nharpercb1adc32016-03-30 16:05:48 -07001544 def ForwardTokenBindingHeader(self):
nharpere758cd12016-07-13 17:49:36 -07001545 """Send a redirect that sets the Include-Referred-Token-Binding-ID
nharpercb1adc32016-03-30 16:05:48 -07001546 header."""
1547
1548 test_name = '/forward-tokbind'
1549 if not self._ShouldHandleRequest(test_name):
1550 return False
1551
1552 query_char = self.path.find('?')
1553 if query_char < 0 or len(self.path) <= query_char + 1:
1554 self.sendRedirectHelp(test_name)
1555 return True
1556 dest = urllib.unquote(self.path[query_char + 1:])
1557
1558 self.send_response(302)
1559 self.send_header('Location', dest)
nharpere758cd12016-07-13 17:49:36 -07001560 self.send_header('Include-Referred-Token-Binding-ID', 'true')
nharpercb1adc32016-03-30 16:05:48 -07001561 self.end_headers()
1562 return True
1563
pneubeckfd4f0442015-08-07 04:55:10 -07001564 def GetClientCert(self):
1565 """Send a reply whether a client certificate was provided."""
1566
1567 if not self._ShouldHandleRequest('/client-cert'):
1568 return False
1569
1570 self.send_response(200)
1571 self.send_header('Content-Type', 'text/plain')
1572 self.end_headers()
1573
1574 cert_chain = self.server.tlsConnection.session.clientCertChain
1575 if cert_chain != None:
1576 self.wfile.write('got client cert with fingerprint: ' +
1577 cert_chain.getFingerprint())
1578 else:
1579 self.wfile.write('got no client cert')
1580 return True
1581
davidben599e7e72014-09-03 16:19:09 -07001582 def ClientCipherListHandler(self):
1583 """Send a reply containing the cipher suite list that the client
1584 provided. Each cipher suite value is serialized in decimal, followed by a
1585 newline."""
1586
1587 if not self._ShouldHandleRequest('/client-cipher-list'):
1588 return False
1589
1590 self.send_response(200)
1591 self.send_header('Content-Type', 'text/plain')
1592 self.end_headers()
1593
davidben11682512014-10-06 21:09:11 -07001594 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1595 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001596 return True
1597
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001598 def CloseSocketHandler(self):
1599 """Closes the socket without sending anything."""
1600
1601 if not self._ShouldHandleRequest('/close-socket'):
1602 return False
1603
1604 self.wfile.close()
1605 return True
1606
initial.commit94958cf2008-07-26 22:42:52 +00001607 def DefaultResponseHandler(self):
1608 """This is the catch-all response handler for requests that aren't handled
1609 by one of the special handlers above.
1610 Note that we specify the content-length as without it the https connection
1611 is not closed properly (and the browser keeps expecting data)."""
1612
1613 contents = "Default response given for path: " + self.path
1614 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001615 self.send_header('Content-Type', 'text/html')
1616 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001617 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001618 if (self.command != 'HEAD'):
1619 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001620 return True
1621
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001622 def RedirectConnectHandler(self):
1623 """Sends a redirect to the CONNECT request for www.redirect.com. This
1624 response is not specified by the RFC, so the browser should not follow
1625 the redirect."""
1626
1627 if (self.path.find("www.redirect.com") < 0):
1628 return False
1629
1630 dest = "http://www.destination.com/foo.js"
1631
1632 self.send_response(302) # moved temporarily
1633 self.send_header('Location', dest)
1634 self.send_header('Connection', 'close')
1635 self.end_headers()
1636 return True
1637
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001638 def ServerAuthConnectHandler(self):
1639 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1640 response doesn't make sense because the proxy server cannot request
1641 server authentication."""
1642
1643 if (self.path.find("www.server-auth.com") < 0):
1644 return False
1645
1646 challenge = 'Basic realm="WallyWorld"'
1647
1648 self.send_response(401) # unauthorized
1649 self.send_header('WWW-Authenticate', challenge)
1650 self.send_header('Connection', 'close')
1651 self.end_headers()
1652 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001653
1654 def DefaultConnectResponseHandler(self):
1655 """This is the catch-all response handler for CONNECT requests that aren't
1656 handled by one of the special handlers above. Real Web servers respond
1657 with 400 to CONNECT requests."""
1658
1659 contents = "Your client has issued a malformed or illegal request."
1660 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001661 self.send_header('Content-Type', 'text/html')
1662 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001663 self.end_headers()
1664 self.wfile.write(contents)
1665 return True
1666
initial.commit94958cf2008-07-26 22:42:52 +00001667 # called by the redirect handling function when there is no parameter
1668 def sendRedirectHelp(self, redirect_name):
1669 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001670 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001671 self.end_headers()
1672 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1673 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1674 self.wfile.write('</body></html>')
1675
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001676 # called by chunked handling function
1677 def sendChunkHelp(self, chunk):
1678 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1679 self.wfile.write('%X\r\n' % len(chunk))
1680 self.wfile.write(chunk)
1681 self.wfile.write('\r\n')
1682
akalin@chromium.org154bb132010-11-12 02:20:27 +00001683
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001684class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001685 def __init__(self, request, client_address, socket_server):
mattm10ede842016-11-29 11:57:16 -08001686 handlers = [self.OCSPResponse, self.CaIssuersResponse]
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001687 self.ocsp_response = socket_server.ocsp_response
mattm10ede842016-11-29 11:57:16 -08001688 self.ca_issuers_response = socket_server.ca_issuers_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001689 testserver_base.BasePageHandler.__init__(self, request, client_address,
1690 socket_server, [], handlers, [],
1691 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001692
1693 def OCSPResponse(self):
mattm10ede842016-11-29 11:57:16 -08001694 if not self._ShouldHandleRequest("/ocsp"):
1695 return False
1696 print 'handling ocsp request'
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001697 self.send_response(200)
1698 self.send_header('Content-Type', 'application/ocsp-response')
1699 self.send_header('Content-Length', str(len(self.ocsp_response)))
1700 self.end_headers()
1701
1702 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001703
mattm10ede842016-11-29 11:57:16 -08001704 def CaIssuersResponse(self):
1705 if not self._ShouldHandleRequest("/ca_issuers"):
1706 return False
1707 print 'handling ca_issuers request'
1708 self.send_response(200)
1709 self.send_header('Content-Type', 'application/pkix-cert')
1710 self.send_header('Content-Length', str(len(self.ca_issuers_response)))
1711 self.end_headers()
1712
1713 self.wfile.write(self.ca_issuers_response)
1714
mattm@chromium.org830a3712012-11-07 23:00:07 +00001715
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001716class TCPEchoHandler(SocketServer.BaseRequestHandler):
1717 """The RequestHandler class for TCP echo server.
1718
1719 It is instantiated once per connection to the server, and overrides the
1720 handle() method to implement communication to the client.
1721 """
1722
1723 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001724 """Handles the request from the client and constructs a response."""
1725
1726 data = self.request.recv(65536).strip()
1727 # Verify the "echo request" message received from the client. Send back
1728 # "echo response" message if "echo request" message is valid.
1729 try:
1730 return_data = echo_message.GetEchoResponseData(data)
1731 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001732 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001733 except ValueError:
1734 return
1735
1736 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001737
1738
1739class UDPEchoHandler(SocketServer.BaseRequestHandler):
1740 """The RequestHandler class for UDP echo server.
1741
1742 It is instantiated once per connection to the server, and overrides the
1743 handle() method to implement communication to the client.
1744 """
1745
1746 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001747 """Handles the request from the client and constructs a response."""
1748
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001749 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001750 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001751 # Verify the "echo request" message received from the client. Send back
1752 # "echo response" message if "echo request" message is valid.
1753 try:
1754 return_data = echo_message.GetEchoResponseData(data)
1755 if not return_data:
1756 return
1757 except ValueError:
1758 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001759 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001760
1761
bashi@chromium.org33233532012-09-08 17:37:24 +00001762class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1763 """A request handler that behaves as a proxy server which requires
1764 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1765 """
1766
1767 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1768
1769 def parse_request(self):
1770 """Overrides parse_request to check credential."""
1771
1772 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1773 return False
1774
1775 auth = self.headers.getheader('Proxy-Authorization')
1776 if auth != self._AUTH_CREDENTIAL:
1777 self.send_response(407)
1778 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1779 self.end_headers()
1780 return False
1781
1782 return True
1783
1784 def _start_read_write(self, sock):
1785 sock.setblocking(0)
1786 self.request.setblocking(0)
1787 rlist = [self.request, sock]
1788 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001789 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001790 if errors:
1791 self.send_response(500)
1792 self.end_headers()
1793 return
1794 for s in ready_sockets:
1795 received = s.recv(1024)
1796 if len(received) == 0:
1797 return
1798 if s == self.request:
1799 other = sock
1800 else:
1801 other = self.request
1802 other.send(received)
1803
1804 def _do_common_method(self):
1805 url = urlparse.urlparse(self.path)
1806 port = url.port
1807 if not port:
1808 if url.scheme == 'http':
1809 port = 80
1810 elif url.scheme == 'https':
1811 port = 443
1812 if not url.hostname or not port:
1813 self.send_response(400)
1814 self.end_headers()
1815 return
1816
1817 if len(url.path) == 0:
1818 path = '/'
1819 else:
1820 path = url.path
1821 if len(url.query) > 0:
1822 path = '%s?%s' % (url.path, url.query)
1823
1824 sock = None
1825 try:
1826 sock = socket.create_connection((url.hostname, port))
1827 sock.send('%s %s %s\r\n' % (
1828 self.command, path, self.protocol_version))
1829 for header in self.headers.headers:
1830 header = header.strip()
1831 if (header.lower().startswith('connection') or
1832 header.lower().startswith('proxy')):
1833 continue
1834 sock.send('%s\r\n' % header)
1835 sock.send('\r\n')
1836 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001837 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001838 self.send_response(500)
1839 self.end_headers()
1840 finally:
1841 if sock is not None:
1842 sock.close()
1843
1844 def do_CONNECT(self):
1845 try:
1846 pos = self.path.rfind(':')
1847 host = self.path[:pos]
1848 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001849 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001850 self.send_response(400)
1851 self.end_headers()
1852
1853 try:
1854 sock = socket.create_connection((host, port))
1855 self.send_response(200, 'Connection established')
1856 self.end_headers()
1857 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001858 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001859 self.send_response(500)
1860 self.end_headers()
1861 finally:
1862 sock.close()
1863
1864 def do_GET(self):
1865 self._do_common_method()
1866
1867 def do_HEAD(self):
1868 self._do_common_method()
1869
1870
mattm@chromium.org830a3712012-11-07 23:00:07 +00001871class ServerRunner(testserver_base.TestServerRunner):
1872 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001873
mattm@chromium.org830a3712012-11-07 23:00:07 +00001874 def __init__(self):
1875 super(ServerRunner, self).__init__()
1876 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001877
mattm@chromium.org830a3712012-11-07 23:00:07 +00001878 def __make_data_dir(self):
1879 if self.options.data_dir:
1880 if not os.path.isdir(self.options.data_dir):
1881 raise testserver_base.OptionError('specified data dir not found: ' +
1882 self.options.data_dir + ' exiting...')
1883 my_data_dir = self.options.data_dir
1884 else:
1885 # Create the default path to our data dir, relative to the exe dir.
1886 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1887 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001888
mattm@chromium.org830a3712012-11-07 23:00:07 +00001889 #TODO(ibrar): Must use Find* funtion defined in google\tools
1890 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001891
mattm@chromium.org830a3712012-11-07 23:00:07 +00001892 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001893
mattm@chromium.org830a3712012-11-07 23:00:07 +00001894 def create_server(self, server_data):
1895 port = self.options.port
1896 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001897
estark21667d62015-04-08 21:00:16 -07001898 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1899 # will result in a call to |getaddrinfo|, which fails with "nodename
1900 # nor servname provided" for localhost:0 on 10.6.
1901 if self.options.server_type == SERVER_WEBSOCKET and \
1902 host == "localhost" and \
1903 port == 0:
1904 host = "127.0.0.1"
1905
mattm@chromium.org830a3712012-11-07 23:00:07 +00001906 if self.options.server_type == SERVER_HTTP:
1907 if self.options.https:
1908 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001909 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001910 if self.options.cert_and_key_file:
1911 if not os.path.isfile(self.options.cert_and_key_file):
1912 raise testserver_base.OptionError(
1913 'specified server cert file not found: ' +
1914 self.options.cert_and_key_file + ' exiting...')
1915 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
mattm10ede842016-11-29 11:57:16 -08001916 elif self.options.aia_intermediate:
1917 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1918 print ('AIA server started on %s:%d...' %
1919 (host, self.__ocsp_server.server_port))
1920
1921 (pem_cert_and_key, intermediate_cert_der) = \
1922 minica.GenerateCertKeyAndIntermediate(
1923 subject = "127.0.0.1",
1924 ca_issuers_url = ("http://%s:%d/ca_issuers" %
1925 (host, self.__ocsp_server.server_port)),
1926 serial = self.options.cert_serial)
1927
1928 self.__ocsp_server.ocsp_response = None
1929 self.__ocsp_server.ca_issuers_response = intermediate_cert_der
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001930 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001931 # generate a new certificate and run an OCSP server for it.
1932 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001933 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001934 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001935
dadrian4ccf51c2016-07-20 15:36:58 -07001936 ocsp_states = list()
1937 for ocsp_state_arg in self.options.ocsp.split(':'):
1938 if ocsp_state_arg == 'ok':
1939 ocsp_state = minica.OCSP_STATE_GOOD
1940 elif ocsp_state_arg == 'revoked':
1941 ocsp_state = minica.OCSP_STATE_REVOKED
1942 elif ocsp_state_arg == 'invalid':
1943 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE
1944 elif ocsp_state_arg == 'unauthorized':
1945 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1946 elif ocsp_state_arg == 'unknown':
1947 ocsp_state = minica.OCSP_STATE_UNKNOWN
1948 elif ocsp_state_arg == 'later':
1949 ocsp_state = minica.OCSP_STATE_TRY_LATER
1950 elif ocsp_state_arg == 'invalid_data':
1951 ocsp_state = minica.OCSP_STATE_INVALID_RESPONSE_DATA
1952 elif ocsp_state_arg == "mismatched_serial":
1953 ocsp_state = minica.OCSP_STATE_MISMATCHED_SERIAL
1954 else:
1955 raise testserver_base.OptionError('unknown OCSP status: ' +
1956 ocsp_state_arg)
1957 ocsp_states.append(ocsp_state)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001958
dadrian4ccf51c2016-07-20 15:36:58 -07001959 if len(ocsp_states) > 1:
1960 if set(ocsp_states) & OCSP_STATES_NO_SINGLE_RESPONSE:
1961 raise testserver_base.OptionError('Multiple OCSP responses '
1962 'incompatible with states ' + str(ocsp_states))
1963
1964 ocsp_dates = list()
1965 for ocsp_date_arg in self.options.ocsp_date.split(':'):
1966 if ocsp_date_arg == 'valid':
1967 ocsp_date = minica.OCSP_DATE_VALID
1968 elif ocsp_date_arg == 'old':
1969 ocsp_date = minica.OCSP_DATE_OLD
1970 elif ocsp_date_arg == 'early':
1971 ocsp_date = minica.OCSP_DATE_EARLY
1972 elif ocsp_date_arg == 'long':
1973 ocsp_date = minica.OCSP_DATE_LONG
dadrian4ccf51c2016-07-20 15:36:58 -07001974 else:
1975 raise testserver_base.OptionError('unknown OCSP date: ' +
1976 ocsp_date_arg)
1977 ocsp_dates.append(ocsp_date)
1978
1979 if len(ocsp_states) != len(ocsp_dates):
1980 raise testserver_base.OptionError('mismatched ocsp and ocsp-date '
1981 'count')
1982
1983 ocsp_produced = None
1984 if self.options.ocsp_produced == 'valid':
1985 ocsp_produced = minica.OCSP_PRODUCED_VALID
1986 elif self.options.ocsp_produced == 'before':
1987 ocsp_produced = minica.OCSP_PRODUCED_BEFORE_CERT
1988 elif self.options.ocsp_produced == 'after':
1989 ocsp_produced = minica.OCSP_PRODUCED_AFTER_CERT
mattm@chromium.org830a3712012-11-07 23:00:07 +00001990 else:
dadrian4ccf51c2016-07-20 15:36:58 -07001991 raise testserver_base.OptionError('unknown OCSP produced: ' +
1992 self.options.ocsp_produced)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001993
mattm@chromium.org830a3712012-11-07 23:00:07 +00001994 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1995 subject = "127.0.0.1",
1996 ocsp_url = ("http://%s:%d/ocsp" %
1997 (host, self.__ocsp_server.server_port)),
dadrian4ccf51c2016-07-20 15:36:58 -07001998 ocsp_states = ocsp_states,
1999 ocsp_dates = ocsp_dates,
2000 ocsp_produced = ocsp_produced,
agl@chromium.orgdf778142013-07-31 21:57:28 +00002001 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002
davidben3e2564a2014-11-07 18:51:00 -08002003 if self.options.ocsp_server_unavailable:
2004 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2005 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2006 else:
2007 self.__ocsp_server.ocsp_response = ocsp_der
mattm10ede842016-11-29 11:57:16 -08002008 self.__ocsp_server.ca_issuers_response = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009
2010 for ca_cert in self.options.ssl_client_ca:
2011 if not os.path.isfile(ca_cert):
2012 raise testserver_base.OptionError(
2013 'specified trusted client CA file not found: ' + ca_cert +
2014 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002015
2016 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002017 if self.options.staple_ocsp_response:
2018 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002019
mattm@chromium.org830a3712012-11-07 23:00:07 +00002020 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2021 self.options.ssl_client_auth,
2022 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002023 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002024 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002025 self.options.ssl_key_exchange,
bnc5fb33bd2016-08-05 12:09:21 -07002026 self.options.alpn_protocols,
bnc609ad4c2015-10-02 05:11:24 -07002027 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002028 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002029 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002030 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002031 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002032 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002033 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002034 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002035 self.options.alert_after_handshake,
2036 self.options.disable_channel_id,
2037 self.options.disable_extended_master_secret,
2038 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002039 print 'HTTPS server started on https://%s:%d...' % \
2040 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002041 else:
2042 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002043 print 'HTTP server started on http://%s:%d...' % \
2044 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002045
2046 server.data_dir = self.__make_data_dir()
2047 server.file_root_url = self.options.file_root_url
2048 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002049 elif self.options.server_type == SERVER_WEBSOCKET:
2050 # Launch pywebsocket via WebSocketServer.
2051 logger = logging.getLogger()
2052 logger.addHandler(logging.StreamHandler())
2053 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2054 # is required to work correctly. It should be fixed from pywebsocket side.
2055 os.chdir(self.__make_data_dir())
2056 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002057 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002058 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002059 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002060 websocket_options.use_tls = True
2061 websocket_options.private_key = self.options.cert_and_key_file
2062 websocket_options.certificate = self.options.cert_and_key_file
2063 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002064 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002065 websocket_options.tls_client_auth = True
2066 if len(self.options.ssl_client_ca) != 1:
2067 raise testserver_base.OptionError(
2068 'one trusted client CA file should be specified')
2069 if not os.path.isfile(self.options.ssl_client_ca[0]):
2070 raise testserver_base.OptionError(
2071 'specified trusted client CA file not found: ' +
2072 self.options.ssl_client_ca[0] + ' exiting...')
2073 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002074 print 'Trying to start websocket server on %s://%s:%d...' % \
2075 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002076 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002077 print 'WebSocket server started on %s://%s:%d...' % \
2078 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002079 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002080 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002081 elif self.options.server_type == SERVER_TCP_ECHO:
2082 # Used for generating the key (randomly) that encodes the "echo request"
2083 # message.
2084 random.seed()
2085 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002086 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002087 server_data['port'] = server.server_port
2088 elif self.options.server_type == SERVER_UDP_ECHO:
2089 # Used for generating the key (randomly) that encodes the "echo request"
2090 # message.
2091 random.seed()
2092 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002093 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002094 server_data['port'] = server.server_port
2095 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2096 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002097 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002098 server_data['port'] = server.server_port
2099 elif self.options.server_type == SERVER_FTP:
2100 my_data_dir = self.__make_data_dir()
2101
2102 # Instantiate a dummy authorizer for managing 'virtual' users
2103 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2104
xleng9d4c45f2015-05-04 16:26:12 -07002105 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002106 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2107
xleng9d4c45f2015-05-04 16:26:12 -07002108 # Define a read-only anonymous user unless disabled
2109 if not self.options.no_anonymous_ftp_user:
2110 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002111
2112 # Instantiate FTP handler class
2113 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2114 ftp_handler.authorizer = authorizer
2115
2116 # Define a customized banner (string returned when client connects)
2117 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2118 pyftpdlib.ftpserver.__ver__)
2119
2120 # Instantiate FTP server class and listen to address:port
2121 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2122 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002123 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002124 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 raise testserver_base.OptionError('unknown server type' +
2126 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002127
mattm@chromium.org830a3712012-11-07 23:00:07 +00002128 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002129
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130 def run_server(self):
2131 if self.__ocsp_server:
2132 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002133
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002135
mattm@chromium.org830a3712012-11-07 23:00:07 +00002136 if self.__ocsp_server:
2137 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002138
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 def add_options(self):
2140 testserver_base.TestServerRunner.add_options(self)
2141 self.option_parser.add_option('-f', '--ftp', action='store_const',
2142 const=SERVER_FTP, default=SERVER_HTTP,
2143 dest='server_type',
2144 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002145 self.option_parser.add_option('--tcp-echo', action='store_const',
2146 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2147 dest='server_type',
2148 help='start up a tcp echo server.')
2149 self.option_parser.add_option('--udp-echo', action='store_const',
2150 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2151 dest='server_type',
2152 help='start up a udp echo server.')
2153 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2154 const=SERVER_BASIC_AUTH_PROXY,
2155 default=SERVER_HTTP, dest='server_type',
2156 help='start up a proxy server which requires '
2157 'basic authentication.')
2158 self.option_parser.add_option('--websocket', action='store_const',
2159 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2160 dest='server_type',
2161 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002162 self.option_parser.add_option('--https', action='store_true',
2163 dest='https', help='Specify that https '
2164 'should be used.')
2165 self.option_parser.add_option('--cert-and-key-file',
2166 dest='cert_and_key_file', help='specify the '
2167 'path to the file containing the certificate '
2168 'and private key for the server in PEM '
2169 'format')
mattm10ede842016-11-29 11:57:16 -08002170 self.option_parser.add_option('--aia-intermediate', action='store_true',
2171 dest='aia_intermediate',
2172 help='generate a certificate chain that '
2173 'requires AIA cert fetching, and run a '
2174 'server to respond to the AIA request.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002175 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2176 help='The type of OCSP response generated '
2177 'for the automatically generated '
2178 'certificate. One of [ok,revoked,invalid]')
dadrian4ccf51c2016-07-20 15:36:58 -07002179 self.option_parser.add_option('--ocsp-date', dest='ocsp_date',
2180 default='valid', help='The validity of the '
2181 'range between thisUpdate and nextUpdate')
2182 self.option_parser.add_option('--ocsp-produced', dest='ocsp_produced',
2183 default='valid', help='producedAt relative '
2184 'to certificate expiry')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002185 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2186 default=0, type=int,
2187 help='If non-zero then the generated '
2188 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002189 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2190 default='0', type='int',
2191 help='If nonzero, certain TLS connections '
2192 'will be aborted in order to test version '
2193 'fallback. 1 means all TLS versions will be '
2194 'aborted. 2 means TLS 1.1 or higher will be '
2195 'aborted. 3 means TLS 1.2 or higher will be '
davidbendf2e3862017-04-12 15:23:34 -07002196 'aborted. 4 means TLS 1.3 or higher will be '
mattm@chromium.org830a3712012-11-07 23:00:07 +00002197 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002198 self.option_parser.add_option('--tls-intolerance-type',
2199 dest='tls_intolerance_type',
2200 default="alert",
2201 help='Controls how the server reacts to a '
2202 'TLS version it is intolerant to. Valid '
2203 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002204 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2205 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002206 default='',
2207 help='Base64 encoded SCT list. If set, '
2208 'server will respond with a '
2209 'signed_certificate_timestamp TLS extension '
2210 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002211 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2212 default=False, const=True,
2213 action='store_const',
2214 help='If given, TLS_FALLBACK_SCSV support '
2215 'will be enabled. This causes the server to '
2216 'reject fallback connections from compatible '
2217 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002218 self.option_parser.add_option('--staple-ocsp-response',
2219 dest='staple_ocsp_response',
2220 default=False, action='store_true',
2221 help='If set, server will staple the OCSP '
2222 'response whenever OCSP is on and the client '
2223 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002224 self.option_parser.add_option('--https-record-resume',
2225 dest='record_resume', const=True,
2226 default=False, action='store_const',
2227 help='Record resumption cache events rather '
2228 'than resuming as normal. Allows the use of '
2229 'the /ssl-session-cache request')
2230 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2231 help='Require SSL client auth on every '
2232 'connection.')
2233 self.option_parser.add_option('--ssl-client-ca', action='append',
2234 default=[], help='Specify that the client '
2235 'certificate request should include the CA '
2236 'named in the subject of the DER-encoded '
2237 'certificate contained in the specified '
2238 'file. This option may appear multiple '
2239 'times, indicating multiple CA names should '
2240 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002241 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2242 default=[], help='Specify that the client '
2243 'certificate request should include the '
2244 'specified certificate_type value. This '
2245 'option may appear multiple times, '
2246 'indicating multiple values should be send '
2247 'in the request. Valid values are '
2248 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2249 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002250 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2251 help='Specify the bulk encryption '
2252 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002253 'SSL server. Valid values are "aes128gcm", '
2254 '"aes256", "aes128", "3des", "rc4". If '
2255 'omitted, all algorithms will be used. This '
2256 'option may appear multiple times, '
2257 'indicating multiple algorithms should be '
2258 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002259 self.option_parser.add_option('--ssl-key-exchange', action='append',
2260 help='Specify the key exchange algorithm(s)'
2261 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002262 'Valid values are "rsa", "dhe_rsa", '
2263 '"ecdhe_rsa". If omitted, all algorithms '
2264 'will be used. This option may appear '
2265 'multiple times, indicating multiple '
2266 'algorithms should be enabled.');
bnc5fb33bd2016-08-05 12:09:21 -07002267 self.option_parser.add_option('--alpn-protocols', action='append',
2268 help='Specify the list of ALPN protocols. '
2269 'The server will not send an ALPN response '
2270 'if this list does not overlap with the '
2271 'list of protocols the client advertises.')
bnc609ad4c2015-10-02 05:11:24 -07002272 self.option_parser.add_option('--npn-protocols', action='append',
bnc5fb33bd2016-08-05 12:09:21 -07002273 help='Specify the list of protocols sent in '
bnc609ad4c2015-10-02 05:11:24 -07002274 'an NPN response. The server will not'
2275 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002276 self.option_parser.add_option('--file-root-url', default='/files/',
2277 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002278 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2279 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2280 dest='ws_basic_auth',
2281 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002282 self.option_parser.add_option('--ocsp-server-unavailable',
2283 dest='ocsp_server_unavailable',
2284 default=False, action='store_true',
2285 help='If set, the OCSP server will return '
2286 'a tryLater status rather than the actual '
2287 'OCSP response.')
davidben21cda342015-03-17 18:04:28 -07002288 self.option_parser.add_option('--alert-after-handshake',
2289 dest='alert_after_handshake',
2290 default=False, action='store_true',
2291 help='If set, the server will send a fatal '
2292 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002293 self.option_parser.add_option('--no-anonymous-ftp-user',
2294 dest='no_anonymous_ftp_user',
2295 default=False, action='store_true',
2296 help='If set, the FTP server will not create '
2297 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002298 self.option_parser.add_option('--disable-channel-id', action='store_true')
2299 self.option_parser.add_option('--disable-extended-master-secret',
2300 action='store_true')
2301 self.option_parser.add_option('--token-binding-params', action='append',
2302 default=[], type='int')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002303
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002304
initial.commit94958cf2008-07-26 22:42:52 +00002305if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002306 sys.exit(ServerRunner().main())