blob: ba130af9943364d9941f55cba3746f2175a799dc [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
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000083class WebSocketOptions:
84 """Holds options for WebSocketServer."""
85
86 def __init__(self, host, port, data_dir):
87 self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
88 self.server_host = host
89 self.port = port
90 self.websock_handlers = data_dir
91 self.scan_dir = None
92 self.allow_handlers_outside_root_dir = False
93 self.websock_handlers_map_file = None
94 self.cgi_directories = []
95 self.is_executable_method = None
96 self.allow_draft75 = False
97 self.strict = True
98
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +000099 self.use_tls = False
100 self.private_key = None
101 self.certificate = None
toyoshim@chromium.orgd532cf32012-10-18 05:05:51 +0000102 self.tls_client_auth = False
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000103 self.tls_client_ca = None
yhirano@chromium.org51f90d92014-03-24 04:49:23 +0000104 self.tls_module = 'ssl'
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000105 self.use_basic_auth = False
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +0000106 self.basic_auth_credential = 'Basic ' + base64.b64encode('test:test')
toyoshim@chromium.orgaa1b6e72012-10-09 03:43:19 +0000107
mattm@chromium.org830a3712012-11-07 23:00:07 +0000108
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000109class RecordingSSLSessionCache(object):
110 """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
111 lookups and inserts in order to test session cache behaviours."""
112
113 def __init__(self):
114 self.log = []
115
116 def __getitem__(self, sessionID):
117 self.log.append(('lookup', sessionID))
118 raise KeyError()
119
120 def __setitem__(self, sessionID, session):
121 self.log.append(('insert', sessionID))
122
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000123
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000124class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
125 testserver_base.BrokenPipeHandlerMixIn,
126 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000127 """This is a specialization of StoppableHTTPServer that adds client
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000128 verification."""
129
130 pass
131
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000132class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
133 testserver_base.BrokenPipeHandlerMixIn,
bauerb@chromium.orgcc71a892012-12-04 21:21:21 +0000134 BaseHTTPServer.HTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000135 """This is a specialization of HTTPServer that serves an
136 OCSP response"""
137
138 def serve_forever_on_thread(self):
139 self.thread = threading.Thread(target = self.serve_forever,
140 name = "OCSPServerThread")
141 self.thread.start()
142
143 def stop_serving(self):
144 self.shutdown()
145 self.thread.join()
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000146
mattm@chromium.org830a3712012-11-07 23:00:07 +0000147
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000148class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000149 testserver_base.ClientRestrictingServerMixIn,
150 testserver_base.BrokenPipeHandlerMixIn,
151 testserver_base.StoppableHTTPServer):
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000152 """This is a specialization of StoppableHTTPServer that add https support and
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000153 client verification."""
initial.commit94958cf2008-07-26 22:42:52 +0000154
agl@chromium.org77a9ad92012-03-20 15:14:27 +0000155 def __init__(self, server_address, request_hander_class, pem_cert_and_key,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000156 ssl_client_auth, ssl_client_cas, ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700157 ssl_bulk_ciphers, ssl_key_exchanges, npn_protocols,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000158 record_resume_info, tls_intolerant,
159 tls_intolerance_type, signed_cert_timestamps,
davidben21cda342015-03-17 18:04:28 -0700160 fallback_scsv_enabled, ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -0700161 alert_after_handshake, disable_channel_id, disable_ems,
162 token_binding_params):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000163 self.cert_chain = tlslite.api.X509CertChain()
164 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000165 # Force using only python implementation - otherwise behavior is different
166 # depending on whether m2crypto Python module is present (error is thrown
167 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
168 # the hood.
169 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
170 private=True,
171 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000172 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000173 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000174 self.ssl_client_cert_types = []
bnc609ad4c2015-10-02 05:11:24 -0700175 self.npn_protocols = npn_protocols
ekasper@google.com24aa8222013-11-28 13:43:26 +0000176 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000177 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000178 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000179
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000180 if ssl_client_auth:
181 for ca_file in ssl_client_cas:
182 s = open(ca_file).read()
183 x509 = tlslite.api.X509()
184 x509.parse(s)
185 self.ssl_client_cas.append(x509.subject)
186
187 for cert_type in ssl_client_cert_types:
188 self.ssl_client_cert_types.append({
189 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000190 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
191 }[cert_type])
192
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000193 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800194 # Enable SSLv3 for testing purposes.
195 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000196 if ssl_bulk_ciphers is not None:
197 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000198 if ssl_key_exchanges is not None:
199 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000200 if tls_intolerant != 0:
201 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
202 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700203 if alert_after_handshake:
204 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700205 if disable_channel_id:
206 self.ssl_handshake_settings.enableChannelID = False
207 if disable_ems:
208 self.ssl_handshake_settings.enableExtendedMasterSecret = False
209 self.ssl_handshake_settings.supportedTokenBindingParams = \
210 token_binding_params
initial.commit94958cf2008-07-26 22:42:52 +0000211
rsleevi8146efa2015-03-16 12:31:24 -0700212 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000213 # If record_resume_info is true then we'll replace the session cache with
214 # an object that records the lookups and inserts that it sees.
215 self.session_cache = RecordingSSLSessionCache()
216 else:
217 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000218 testserver_base.StoppableHTTPServer.__init__(self,
219 server_address,
220 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000221
222 def handshake(self, tlsConnection):
223 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000224
initial.commit94958cf2008-07-26 22:42:52 +0000225 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000226 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000227 tlsConnection.handshakeServer(certChain=self.cert_chain,
228 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000229 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000230 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000231 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000232 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000233 reqCertTypes=self.ssl_client_cert_types,
bnc609ad4c2015-10-02 05:11:24 -0700234 nextProtos=self.npn_protocols,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000235 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000236 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000237 fallbackSCSV=self.fallback_scsv_enabled,
238 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000239 tlsConnection.ignoreAbruptClose = True
240 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000241 except tlslite.api.TLSAbruptCloseError:
242 # Ignore abrupt close.
243 return True
initial.commit94958cf2008-07-26 22:42:52 +0000244 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000245 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000246 return False
247
akalin@chromium.org154bb132010-11-12 02:20:27 +0000248
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000249class FTPServer(testserver_base.ClientRestrictingServerMixIn,
250 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000251 """This is a specialization of FTPServer that adds client verification."""
252
253 pass
254
255
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000256class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
257 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000258 """A TCP echo server that echoes back what it has received."""
259
260 def server_bind(self):
261 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000262
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000263 SocketServer.TCPServer.server_bind(self)
264 host, port = self.socket.getsockname()[:2]
265 self.server_name = socket.getfqdn(host)
266 self.server_port = port
267
268 def serve_forever(self):
269 self.stop = False
270 self.nonce_time = None
271 while not self.stop:
272 self.handle_request()
273 self.socket.close()
274
275
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000276class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
277 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000278 """A UDP echo server that echoes back what it has received."""
279
280 def server_bind(self):
281 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000282
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000283 SocketServer.UDPServer.server_bind(self)
284 host, port = self.socket.getsockname()[:2]
285 self.server_name = socket.getfqdn(host)
286 self.server_port = port
287
288 def serve_forever(self):
289 self.stop = False
290 self.nonce_time = None
291 while not self.stop:
292 self.handle_request()
293 self.socket.close()
294
295
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000296class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000297 # Class variables to allow for persistence state between page handler
298 # invocations
299 rst_limits = {}
300 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000301
302 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000304 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000305 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000306 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000307 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000308 self.NoCacheMaxAgeTimeHandler,
309 self.NoCacheTimeHandler,
310 self.CacheTimeHandler,
311 self.CacheExpiresHandler,
312 self.CacheProxyRevalidateHandler,
313 self.CachePrivateHandler,
314 self.CachePublicHandler,
315 self.CacheSMaxAgeHandler,
316 self.CacheMustRevalidateHandler,
317 self.CacheMustRevalidateMaxAgeHandler,
318 self.CacheNoStoreHandler,
319 self.CacheNoStoreMaxAgeHandler,
320 self.CacheNoTransformHandler,
321 self.DownloadHandler,
322 self.DownloadFinishHandler,
323 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000324 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000325 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000326 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000328 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000329 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000330 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000331 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000332 self.AuthBasicHandler,
333 self.AuthDigestHandler,
334 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000335 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000336 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000337 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700338 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000339 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000340 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000341 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000342 self.GetChannelID,
nharper08eae822016-01-25 15:54:14 -0800343 self.GetTokenBindingEKM,
nharpercb1adc32016-03-30 16:05:48 -0700344 self.ForwardTokenBindingHeader,
pneubeckfd4f0442015-08-07 04:55:10 -0700345 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700346 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000347 self.CloseSocketHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000348 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000349 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000350 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000351 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000352 self.PostOnlyFileHandler,
353 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000354 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000355 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000356 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000357 head_handlers = [
358 self.FileHandler,
359 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000360
maruel@google.come250a9b2009-03-10 17:39:46 +0000361 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000362 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000363 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000364 'gif': 'image/gif',
365 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000366 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700367 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000368 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000369 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000370 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000371 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000372 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000373 }
initial.commit94958cf2008-07-26 22:42:52 +0000374 self._default_mime_type = 'text/html'
375
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000376 testserver_base.BasePageHandler.__init__(self, request, client_address,
377 socket_server, connect_handlers,
378 get_handlers, head_handlers,
379 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000380
initial.commit94958cf2008-07-26 22:42:52 +0000381 def GetMIMETypeFromName(self, file_name):
382 """Returns the mime type for the specified file_name. So far it only looks
383 at the file extension."""
384
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000385 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000386 if len(extension) == 0:
387 # no extension.
388 return self._default_mime_type
389
ericroman@google.comc17ca532009-05-07 03:51:05 +0000390 # extension starts with a dot, so we need to remove it
391 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000392
initial.commit94958cf2008-07-26 22:42:52 +0000393 def NoCacheMaxAgeTimeHandler(self):
394 """This request handler yields a page with the title set to the current
395 system time, and no caching requested."""
396
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000397 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000398 return False
399
400 self.send_response(200)
401 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000402 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000403 self.end_headers()
404
maruel@google.come250a9b2009-03-10 17:39:46 +0000405 self.wfile.write('<html><head><title>%s</title></head></html>' %
406 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000407
408 return True
409
410 def NoCacheTimeHandler(self):
411 """This request handler yields a page with the title set to the current
412 system time, and no caching requested."""
413
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000414 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000415 return False
416
417 self.send_response(200)
418 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000419 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000420 self.end_headers()
421
maruel@google.come250a9b2009-03-10 17:39:46 +0000422 self.wfile.write('<html><head><title>%s</title></head></html>' %
423 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000424
425 return True
426
427 def CacheTimeHandler(self):
428 """This request handler yields a page with the title set to the current
429 system time, and allows caching for one minute."""
430
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000431 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000432 return False
433
434 self.send_response(200)
435 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000436 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000437 self.end_headers()
438
maruel@google.come250a9b2009-03-10 17:39:46 +0000439 self.wfile.write('<html><head><title>%s</title></head></html>' %
440 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000441
442 return True
443
444 def CacheExpiresHandler(self):
445 """This request handler yields a page with the title set to the current
446 system time, and set the page to expire on 1 Jan 2099."""
447
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000448 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000449 return False
450
451 self.send_response(200)
452 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000453 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000454 self.end_headers()
455
maruel@google.come250a9b2009-03-10 17:39:46 +0000456 self.wfile.write('<html><head><title>%s</title></head></html>' %
457 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000458
459 return True
460
461 def CacheProxyRevalidateHandler(self):
462 """This request handler yields a page with the title set to the current
463 system time, and allows caching for 60 seconds"""
464
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000465 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000466 return False
467
468 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000469 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000470 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
471 self.end_headers()
472
maruel@google.come250a9b2009-03-10 17:39:46 +0000473 self.wfile.write('<html><head><title>%s</title></head></html>' %
474 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000475
476 return True
477
478 def CachePrivateHandler(self):
479 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700480 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000481
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000482 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000483 return False
484
485 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000486 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000487 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000488 self.end_headers()
489
maruel@google.come250a9b2009-03-10 17:39:46 +0000490 self.wfile.write('<html><head><title>%s</title></head></html>' %
491 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000492
493 return True
494
495 def CachePublicHandler(self):
496 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700497 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000498
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000499 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000500 return False
501
502 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000503 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000504 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000505 self.end_headers()
506
maruel@google.come250a9b2009-03-10 17:39:46 +0000507 self.wfile.write('<html><head><title>%s</title></head></html>' %
508 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000509
510 return True
511
512 def CacheSMaxAgeHandler(self):
513 """This request handler yields a page with the title set to the current
514 system time, and does not allow for caching."""
515
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000516 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000517 return False
518
519 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000520 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000521 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
522 self.end_headers()
523
maruel@google.come250a9b2009-03-10 17:39:46 +0000524 self.wfile.write('<html><head><title>%s</title></head></html>' %
525 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000526
527 return True
528
529 def CacheMustRevalidateHandler(self):
530 """This request handler yields a page with the title set to the current
531 system time, and does not allow caching."""
532
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000533 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000534 return False
535
536 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000537 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000538 self.send_header('Cache-Control', 'must-revalidate')
539 self.end_headers()
540
maruel@google.come250a9b2009-03-10 17:39:46 +0000541 self.wfile.write('<html><head><title>%s</title></head></html>' %
542 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000543
544 return True
545
546 def CacheMustRevalidateMaxAgeHandler(self):
547 """This request handler yields a page with the title set to the current
548 system time, and does not allow caching event though max-age of 60
549 seconds is specified."""
550
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000551 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000552 return False
553
554 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000555 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000556 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
557 self.end_headers()
558
maruel@google.come250a9b2009-03-10 17:39:46 +0000559 self.wfile.write('<html><head><title>%s</title></head></html>' %
560 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000561
562 return True
563
initial.commit94958cf2008-07-26 22:42:52 +0000564 def CacheNoStoreHandler(self):
565 """This request handler yields a page with the title set to the current
566 system time, and does not allow the page to be stored."""
567
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000568 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000569 return False
570
571 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000572 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000573 self.send_header('Cache-Control', 'no-store')
574 self.end_headers()
575
maruel@google.come250a9b2009-03-10 17:39:46 +0000576 self.wfile.write('<html><head><title>%s</title></head></html>' %
577 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000578
579 return True
580
581 def CacheNoStoreMaxAgeHandler(self):
582 """This request handler yields a page with the title set to the current
583 system time, and does not allow the page to be stored even though max-age
584 of 60 seconds is specified."""
585
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000586 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000587 return False
588
589 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000590 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000591 self.send_header('Cache-Control', 'max-age=60, no-store')
592 self.end_headers()
593
maruel@google.come250a9b2009-03-10 17:39:46 +0000594 self.wfile.write('<html><head><title>%s</title></head></html>' %
595 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000596
597 return True
598
599
600 def CacheNoTransformHandler(self):
601 """This request handler yields a page with the title set to the current
602 system time, and does not allow the content to transformed during
603 user-agent caching"""
604
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000605 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000606 return False
607
608 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000609 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000610 self.send_header('Cache-Control', 'no-transform')
611 self.end_headers()
612
maruel@google.come250a9b2009-03-10 17:39:46 +0000613 self.wfile.write('<html><head><title>%s</title></head></html>' %
614 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000615
616 return True
617
618 def EchoHeader(self):
619 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000620
ananta@chromium.org219b2062009-10-23 16:09:41 +0000621 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000622
ananta@chromium.org56812d02011-04-07 17:52:05 +0000623 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000624 """This function echoes back the value of a specific request header while
625 allowing caching for 16 hours."""
626
ananta@chromium.org56812d02011-04-07 17:52:05 +0000627 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000628
629 def EchoHeaderHelper(self, echo_header):
630 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000631
ananta@chromium.org219b2062009-10-23 16:09:41 +0000632 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000633 return False
634
635 query_char = self.path.find('?')
636 if query_char != -1:
637 header_name = self.path[query_char+1:]
638
639 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000640 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000641 if echo_header == '/echoheadercache':
642 self.send_header('Cache-control', 'max-age=60000')
643 else:
644 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000645 # insert a vary header to properly indicate that the cachability of this
646 # request is subject to value of the request header being echoed.
647 if len(header_name) > 0:
648 self.send_header('Vary', header_name)
649 self.end_headers()
650
651 if len(header_name) > 0:
652 self.wfile.write(self.headers.getheader(header_name))
653
654 return True
655
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000656 def ReadRequestBody(self):
657 """This function reads the body of the current HTTP request, handling
658 both plain and chunked transfer encoded requests."""
659
660 if self.headers.getheader('transfer-encoding') != 'chunked':
661 length = int(self.headers.getheader('content-length'))
662 return self.rfile.read(length)
663
664 # Read the request body as chunks.
665 body = ""
666 while True:
667 line = self.rfile.readline()
668 length = int(line, 16)
669 if length == 0:
670 self.rfile.readline()
671 break
672 body += self.rfile.read(length)
673 self.rfile.read(2)
674 return body
675
initial.commit94958cf2008-07-26 22:42:52 +0000676 def EchoHandler(self):
677 """This handler just echoes back the payload of the request, for testing
678 form submission."""
679
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000680 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000681 return False
682
hirono2838c572015-01-21 12:18:11 -0800683 _, _, _, _, query, _ = urlparse.urlparse(self.path)
684 query_params = cgi.parse_qs(query, True)
685 if 'status' in query_params:
686 self.send_response(int(query_params['status'][0]))
687 else:
688 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000689 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000690 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000691 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000692 return True
693
694 def EchoTitleHandler(self):
695 """This handler is like Echo, but sets the page title to the request."""
696
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000697 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000698 return False
699
700 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000701 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000702 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000703 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000704 self.wfile.write('<html><head><title>')
705 self.wfile.write(request)
706 self.wfile.write('</title></head></html>')
707 return True
708
709 def EchoAllHandler(self):
710 """This handler yields a (more) human-readable page listing information
711 about the request header & contents."""
712
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000713 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000714 return False
715
716 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000717 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000718 self.end_headers()
719 self.wfile.write('<html><head><style>'
720 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
721 '</style></head><body>'
722 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000723 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000724 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000725
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000726 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000727 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000728 params = cgi.parse_qs(qs, keep_blank_values=1)
729
730 for param in params:
731 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000732
733 self.wfile.write('</pre>')
734
735 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
736
737 self.wfile.write('</body></html>')
738 return True
739
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000740 def EchoMultipartPostHandler(self):
741 """This handler echoes received multipart post data as json format."""
742
743 if not (self._ShouldHandleRequest("/echomultipartpost") or
744 self._ShouldHandleRequest("/searchbyimage")):
745 return False
746
747 content_type, parameters = cgi.parse_header(
748 self.headers.getheader('content-type'))
749 if content_type == 'multipart/form-data':
750 post_multipart = cgi.parse_multipart(self.rfile, parameters)
751 elif content_type == 'application/x-www-form-urlencoded':
752 raise Exception('POST by application/x-www-form-urlencoded is '
753 'not implemented.')
754 else:
755 post_multipart = {}
756
757 # Since the data can be binary, we encode them by base64.
758 post_multipart_base64_encoded = {}
759 for field, values in post_multipart.items():
760 post_multipart_base64_encoded[field] = [base64.b64encode(value)
761 for value in values]
762
763 result = {'POST_multipart' : post_multipart_base64_encoded}
764
765 self.send_response(200)
766 self.send_header("Content-type", "text/plain")
767 self.end_headers()
768 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
769 return True
770
initial.commit94958cf2008-07-26 22:42:52 +0000771 def DownloadHandler(self):
772 """This handler sends a downloadable file with or without reporting
773 the size (6K)."""
774
775 if self.path.startswith("/download-unknown-size"):
776 send_length = False
777 elif self.path.startswith("/download-known-size"):
778 send_length = True
779 else:
780 return False
781
782 #
783 # The test which uses this functionality is attempting to send
784 # small chunks of data to the client. Use a fairly large buffer
785 # so that we'll fill chrome's IO buffer enough to force it to
786 # actually write the data.
787 # See also the comments in the client-side of this test in
788 # download_uitest.cc
789 #
790 size_chunk1 = 35*1024
791 size_chunk2 = 10*1024
792
793 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000794 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000795 self.send_header('Cache-Control', 'max-age=0')
796 if send_length:
797 self.send_header('Content-Length', size_chunk1 + size_chunk2)
798 self.end_headers()
799
800 # First chunk of data:
801 self.wfile.write("*" * size_chunk1)
802 self.wfile.flush()
803
804 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000805 self.server.wait_for_download = True
806 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000807 self.server.handle_request()
808
809 # Second chunk of data:
810 self.wfile.write("*" * size_chunk2)
811 return True
812
813 def DownloadFinishHandler(self):
814 """This handler just tells the server to finish the current download."""
815
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000816 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000817 return False
818
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000819 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000820 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000821 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000822 self.send_header('Cache-Control', 'max-age=0')
823 self.end_headers()
824 return True
825
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000826 def _ReplaceFileData(self, data, query_parameters):
827 """Replaces matching substrings in a file.
828
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000829 If the 'replace_text' URL query parameter is present, it is expected to be
830 of the form old_text:new_text, which indicates that any old_text strings in
831 the file are replaced with new_text. Multiple 'replace_text' parameters may
832 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000833
834 If the parameters are not present, |data| is returned.
835 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000836
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000837 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000838 replace_text_values = query_dict.get('replace_text', [])
839 for replace_text_value in replace_text_values:
840 replace_text_args = replace_text_value.split(':')
841 if len(replace_text_args) != 2:
842 raise ValueError(
843 'replace_text must be of form old_text:new_text. Actual value: %s' %
844 replace_text_value)
845 old_text_b64, new_text_b64 = replace_text_args
846 old_text = base64.urlsafe_b64decode(old_text_b64)
847 new_text = base64.urlsafe_b64decode(new_text_b64)
848 data = data.replace(old_text, new_text)
849 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000850
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000851 def ZipFileHandler(self):
852 """This handler sends the contents of the requested file in compressed form.
853 Can pass in a parameter that specifies that the content length be
854 C - the compressed size (OK),
855 U - the uncompressed size (Non-standard, but handled),
856 S - less than compressed (OK because we keep going),
857 M - larger than compressed but less than uncompressed (an error),
858 L - larger than uncompressed (an error)
859 Example: compressedfiles/Picture_1.doc?C
860 """
861
862 prefix = "/compressedfiles/"
863 if not self.path.startswith(prefix):
864 return False
865
866 # Consume a request body if present.
867 if self.command == 'POST' or self.command == 'PUT' :
868 self.ReadRequestBody()
869
870 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
871
872 if not query in ('C', 'U', 'S', 'M', 'L'):
873 return False
874
875 sub_path = url_path[len(prefix):]
876 entries = sub_path.split('/')
877 file_path = os.path.join(self.server.data_dir, *entries)
878 if os.path.isdir(file_path):
879 file_path = os.path.join(file_path, 'index.html')
880
881 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000882 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000883 self.send_error(404)
884 return True
885
886 f = open(file_path, "rb")
887 data = f.read()
888 uncompressed_len = len(data)
889 f.close()
890
891 # Compress the data.
892 data = zlib.compress(data)
893 compressed_len = len(data)
894
895 content_length = compressed_len
896 if query == 'U':
897 content_length = uncompressed_len
898 elif query == 'S':
899 content_length = compressed_len / 2
900 elif query == 'M':
901 content_length = (compressed_len + uncompressed_len) / 2
902 elif query == 'L':
903 content_length = compressed_len + uncompressed_len
904
905 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000906 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000907 self.send_header('Content-encoding', 'deflate')
908 self.send_header('Connection', 'close')
909 self.send_header('Content-Length', content_length)
910 self.send_header('ETag', '\'' + file_path + '\'')
911 self.end_headers()
912
913 self.wfile.write(data)
914
915 return True
916
initial.commit94958cf2008-07-26 22:42:52 +0000917 def FileHandler(self):
918 """This handler sends the contents of the requested file. Wow, it's like
919 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000920
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000921 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000922 if not self.path.startswith(prefix):
923 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000924 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000925
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000926 def PostOnlyFileHandler(self):
927 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000928
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000929 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000930 if not self.path.startswith(prefix):
931 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000932 return self._FileHandlerHelper(prefix)
933
934 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000935 request_body = ''
936 if self.command == 'POST' or self.command == 'PUT':
937 # Consume a request body if present.
938 request_body = self.ReadRequestBody()
939
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000940 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000941 query_dict = cgi.parse_qs(query)
942
943 expected_body = query_dict.get('expected_body', [])
944 if expected_body and request_body not in expected_body:
945 self.send_response(404)
946 self.end_headers()
947 self.wfile.write('')
948 return True
949
950 expected_headers = query_dict.get('expected_headers', [])
951 for expected_header in expected_headers:
952 header_name, expected_value = expected_header.split(':')
953 if self.headers.getheader(header_name) != expected_value:
954 self.send_response(404)
955 self.end_headers()
956 self.wfile.write('')
957 return True
958
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000959 sub_path = url_path[len(prefix):]
960 entries = sub_path.split('/')
961 file_path = os.path.join(self.server.data_dir, *entries)
962 if os.path.isdir(file_path):
963 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000964
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000965 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000966 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000967 self.send_error(404)
968 return True
969
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000970 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000971 data = f.read()
972 f.close()
973
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000974 data = self._ReplaceFileData(data, query)
975
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000976 old_protocol_version = self.protocol_version
977
initial.commit94958cf2008-07-26 22:42:52 +0000978 # If file.mock-http-headers exists, it contains the headers we
979 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000980 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000981 if os.path.isfile(headers_path):
982 f = open(headers_path, "r")
983
984 # "HTTP/1.1 200 OK"
985 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000986 http_major, http_minor, status_code = re.findall(
987 'HTTP/(\d+).(\d+) (\d+)', response)[0]
988 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000989 self.send_response(int(status_code))
990
991 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000992 header_values = re.findall('(\S+):\s*(.*)', line)
993 if len(header_values) > 0:
994 # "name: value"
995 name, value = header_values[0]
996 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000997 f.close()
998 else:
999 # Could be more generic once we support mime-type sniffing, but for
1000 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001001
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001002 range_header = self.headers.get('Range')
1003 if range_header and range_header.startswith('bytes='):
1004 # Note this doesn't handle all valid byte range_header values (i.e.
1005 # left open ended ones), just enough for what we needed so far.
1006 range_header = range_header[6:].split('-')
1007 start = int(range_header[0])
1008 if range_header[1]:
1009 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001010 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001011 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001012
1013 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001014 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1015 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001016 self.send_header('Content-Range', content_range)
1017 data = data[start: end + 1]
1018 else:
1019 self.send_response(200)
1020
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001021 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001022 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001023 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001024 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001025 self.end_headers()
1026
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001027 if (self.command != 'HEAD'):
1028 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001029
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001030 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001031 return True
1032
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001033 def SetCookieHandler(self):
1034 """This handler just sets a cookie, for testing cookie handling."""
1035
1036 if not self._ShouldHandleRequest("/set-cookie"):
1037 return False
1038
1039 query_char = self.path.find('?')
1040 if query_char != -1:
1041 cookie_values = self.path[query_char + 1:].split('&')
1042 else:
1043 cookie_values = ("",)
1044 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001045 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001046 for cookie_value in cookie_values:
1047 self.send_header('Set-Cookie', '%s' % cookie_value)
1048 self.end_headers()
1049 for cookie_value in cookie_values:
1050 self.wfile.write('%s' % cookie_value)
1051 return True
1052
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001053 def SetManyCookiesHandler(self):
1054 """This handler just sets a given number of cookies, for testing handling
1055 of large numbers of cookies."""
1056
1057 if not self._ShouldHandleRequest("/set-many-cookies"):
1058 return False
1059
1060 query_char = self.path.find('?')
1061 if query_char != -1:
1062 num_cookies = int(self.path[query_char + 1:])
1063 else:
1064 num_cookies = 0
1065 self.send_response(200)
1066 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001067 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001068 self.send_header('Set-Cookie', 'a=')
1069 self.end_headers()
1070 self.wfile.write('%d cookies were sent' % num_cookies)
1071 return True
1072
mattm@chromium.org983fc462012-06-30 00:52:08 +00001073 def ExpectAndSetCookieHandler(self):
1074 """Expects some cookies to be sent, and if they are, sets more cookies.
1075
1076 The expect parameter specifies a required cookie. May be specified multiple
1077 times.
1078 The set parameter specifies a cookie to set if all required cookies are
1079 preset. May be specified multiple times.
1080 The data parameter specifies the response body data to be returned."""
1081
1082 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1083 return False
1084
1085 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1086 query_dict = cgi.parse_qs(query)
1087 cookies = set()
1088 if 'Cookie' in self.headers:
1089 cookie_header = self.headers.getheader('Cookie')
1090 cookies.update([s.strip() for s in cookie_header.split(';')])
1091 got_all_expected_cookies = True
1092 for expected_cookie in query_dict.get('expect', []):
1093 if expected_cookie not in cookies:
1094 got_all_expected_cookies = False
1095 self.send_response(200)
1096 self.send_header('Content-Type', 'text/html')
1097 if got_all_expected_cookies:
1098 for cookie_value in query_dict.get('set', []):
1099 self.send_header('Set-Cookie', '%s' % cookie_value)
1100 self.end_headers()
1101 for data_value in query_dict.get('data', []):
1102 self.wfile.write(data_value)
1103 return True
1104
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001105 def SetHeaderHandler(self):
1106 """This handler sets a response header. Parameters are in the
1107 key%3A%20value&key2%3A%20value2 format."""
1108
1109 if not self._ShouldHandleRequest("/set-header"):
1110 return False
1111
1112 query_char = self.path.find('?')
1113 if query_char != -1:
1114 headers_values = self.path[query_char + 1:].split('&')
1115 else:
1116 headers_values = ("",)
1117 self.send_response(200)
1118 self.send_header('Content-Type', 'text/html')
1119 for header_value in headers_values:
1120 header_value = urllib.unquote(header_value)
1121 (key, value) = header_value.split(': ', 1)
1122 self.send_header(key, value)
1123 self.end_headers()
1124 for header_value in headers_values:
1125 self.wfile.write('%s' % header_value)
1126 return True
1127
initial.commit94958cf2008-07-26 22:42:52 +00001128 def AuthBasicHandler(self):
1129 """This handler tests 'Basic' authentication. It just sends a page with
1130 title 'user/pass' if you succeed."""
1131
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001132 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001133 return False
1134
1135 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001136 expected_password = 'secret'
1137 realm = 'testrealm'
1138 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001139
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001140 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1141 query_params = cgi.parse_qs(query, True)
1142 if 'set-cookie-if-challenged' in query_params:
1143 set_cookie_if_challenged = True
1144 if 'password' in query_params:
1145 expected_password = query_params['password'][0]
1146 if 'realm' in query_params:
1147 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001148
initial.commit94958cf2008-07-26 22:42:52 +00001149 auth = self.headers.getheader('authorization')
1150 try:
1151 if not auth:
1152 raise Exception('no auth')
1153 b64str = re.findall(r'Basic (\S+)', auth)[0]
1154 userpass = base64.b64decode(b64str)
1155 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001156 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001157 raise Exception('wrong password')
1158 except Exception, e:
1159 # Authentication failed.
1160 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001161 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001162 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001163 if set_cookie_if_challenged:
1164 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001165 self.end_headers()
1166 self.wfile.write('<html><head>')
1167 self.wfile.write('<title>Denied: %s</title>' % e)
1168 self.wfile.write('</head><body>')
1169 self.wfile.write('auth=%s<p>' % auth)
1170 self.wfile.write('b64str=%s<p>' % b64str)
1171 self.wfile.write('username: %s<p>' % username)
1172 self.wfile.write('userpass: %s<p>' % userpass)
1173 self.wfile.write('password: %s<p>' % password)
1174 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1175 self.wfile.write('</body></html>')
1176 return True
1177
1178 # Authentication successful. (Return a cachable response to allow for
1179 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001180 old_protocol_version = self.protocol_version
1181 self.protocol_version = "HTTP/1.1"
1182
initial.commit94958cf2008-07-26 22:42:52 +00001183 if_none_match = self.headers.getheader('if-none-match')
1184 if if_none_match == "abc":
1185 self.send_response(304)
1186 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001187 elif url_path.endswith(".gif"):
1188 # Using chrome/test/data/google/logo.gif as the test image
1189 test_image_path = ['google', 'logo.gif']
1190 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1191 if not os.path.isfile(gif_path):
1192 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001193 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001194 return True
1195
1196 f = open(gif_path, "rb")
1197 data = f.read()
1198 f.close()
1199
1200 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001201 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001202 self.send_header('Cache-control', 'max-age=60000')
1203 self.send_header('Etag', 'abc')
1204 self.end_headers()
1205 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001206 else:
1207 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001208 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001209 self.send_header('Cache-control', 'max-age=60000')
1210 self.send_header('Etag', 'abc')
1211 self.end_headers()
1212 self.wfile.write('<html><head>')
1213 self.wfile.write('<title>%s/%s</title>' % (username, password))
1214 self.wfile.write('</head><body>')
1215 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001216 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001217 self.wfile.write('</body></html>')
1218
rvargas@google.com54453b72011-05-19 01:11:11 +00001219 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001220 return True
1221
tonyg@chromium.org75054202010-03-31 22:06:10 +00001222 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001223 """Returns a nonce that's stable per request path for the server's lifetime.
1224 This is a fake implementation. A real implementation would only use a given
1225 nonce a single time (hence the name n-once). However, for the purposes of
1226 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001227
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001228 Args:
1229 force_reset: Iff set, the nonce will be changed. Useful for testing the
1230 "stale" response.
1231 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001232
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001233 if force_reset or not self.server.nonce_time:
1234 self.server.nonce_time = time.time()
1235 return hashlib.md5('privatekey%s%d' %
1236 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001237
1238 def AuthDigestHandler(self):
1239 """This handler tests 'Digest' authentication.
1240
1241 It just sends a page with title 'user/pass' if you succeed.
1242
1243 A stale response is sent iff "stale" is present in the request path.
1244 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001245
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001246 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001247 return False
1248
tonyg@chromium.org75054202010-03-31 22:06:10 +00001249 stale = 'stale' in self.path
1250 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001251 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001252 password = 'secret'
1253 realm = 'testrealm'
1254
1255 auth = self.headers.getheader('authorization')
1256 pairs = {}
1257 try:
1258 if not auth:
1259 raise Exception('no auth')
1260 if not auth.startswith('Digest'):
1261 raise Exception('not digest')
1262 # Pull out all the name="value" pairs as a dictionary.
1263 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1264
1265 # Make sure it's all valid.
1266 if pairs['nonce'] != nonce:
1267 raise Exception('wrong nonce')
1268 if pairs['opaque'] != opaque:
1269 raise Exception('wrong opaque')
1270
1271 # Check the 'response' value and make sure it matches our magic hash.
1272 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001273 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001274 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001275 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001276 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001277 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001278 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1279 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001280 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001281
1282 if pairs['response'] != response:
1283 raise Exception('wrong password')
1284 except Exception, e:
1285 # Authentication failed.
1286 self.send_response(401)
1287 hdr = ('Digest '
1288 'realm="%s", '
1289 'domain="/", '
1290 'qop="auth", '
1291 'algorithm=MD5, '
1292 'nonce="%s", '
1293 'opaque="%s"') % (realm, nonce, opaque)
1294 if stale:
1295 hdr += ', stale="TRUE"'
1296 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001297 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001298 self.end_headers()
1299 self.wfile.write('<html><head>')
1300 self.wfile.write('<title>Denied: %s</title>' % e)
1301 self.wfile.write('</head><body>')
1302 self.wfile.write('auth=%s<p>' % auth)
1303 self.wfile.write('pairs=%s<p>' % pairs)
1304 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1305 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1306 self.wfile.write('</body></html>')
1307 return True
1308
1309 # Authentication successful.
1310 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001311 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001312 self.end_headers()
1313 self.wfile.write('<html><head>')
1314 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1315 self.wfile.write('</head><body>')
1316 self.wfile.write('auth=%s<p>' % auth)
1317 self.wfile.write('pairs=%s<p>' % pairs)
1318 self.wfile.write('</body></html>')
1319
1320 return True
1321
1322 def SlowServerHandler(self):
1323 """Wait for the user suggested time before responding. The syntax is
1324 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001325
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001326 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001327 return False
1328 query_char = self.path.find('?')
1329 wait_sec = 1.0
1330 if query_char >= 0:
1331 try:
davidben05f82202015-03-31 13:48:07 -07001332 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001333 except ValueError:
1334 pass
1335 time.sleep(wait_sec)
1336 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001337 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001338 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001339 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001340 return True
1341
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001342 def ChunkedServerHandler(self):
1343 """Send chunked response. Allows to specify chunks parameters:
1344 - waitBeforeHeaders - ms to wait before sending headers
1345 - waitBetweenChunks - ms to wait between chunks
1346 - chunkSize - size of each chunk in bytes
1347 - chunksNumber - number of chunks
1348 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1349 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001350
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001351 if not self._ShouldHandleRequest("/chunked"):
1352 return False
1353 query_char = self.path.find('?')
1354 chunkedSettings = {'waitBeforeHeaders' : 0,
1355 'waitBetweenChunks' : 0,
1356 'chunkSize' : 5,
1357 'chunksNumber' : 5}
1358 if query_char >= 0:
1359 params = self.path[query_char + 1:].split('&')
1360 for param in params:
1361 keyValue = param.split('=')
1362 if len(keyValue) == 2:
1363 try:
1364 chunkedSettings[keyValue[0]] = int(keyValue[1])
1365 except ValueError:
1366 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001367 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001368 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1369 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001370 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001371 self.send_header('Connection', 'close')
1372 self.send_header('Transfer-Encoding', 'chunked')
1373 self.end_headers()
1374 # Chunked encoding: sending all chunks, then final zero-length chunk and
1375 # then final CRLF.
1376 for i in range(0, chunkedSettings['chunksNumber']):
1377 if i > 0:
1378 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1379 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001380 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001381 self.sendChunkHelp('')
1382 return True
1383
creis@google.com2f4f6a42011-03-25 19:44:19 +00001384 def NoContentHandler(self):
1385 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001386
creis@google.com2f4f6a42011-03-25 19:44:19 +00001387 if not self._ShouldHandleRequest("/nocontent"):
1388 return False
1389 self.send_response(204)
1390 self.end_headers()
1391 return True
1392
initial.commit94958cf2008-07-26 22:42:52 +00001393 def ServerRedirectHandler(self):
1394 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001395 '/server-redirect?http://foo.bar/asdf' to redirect to
1396 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001397
1398 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001399 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001400 return False
1401
1402 query_char = self.path.find('?')
1403 if query_char < 0 or len(self.path) <= query_char + 1:
1404 self.sendRedirectHelp(test_name)
1405 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001406 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001407
1408 self.send_response(301) # moved permanently
1409 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001410 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001411 self.end_headers()
1412 self.wfile.write('<html><head>')
1413 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1414
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001415 return True
initial.commit94958cf2008-07-26 22:42:52 +00001416
naskoe7a0d0d2014-09-29 08:53:05 -07001417 def CrossSiteRedirectHandler(self):
1418 """Sends a server redirect to the given site. The syntax is
1419 '/cross-site/hostname/...' to redirect to //hostname/...
1420 It is used to navigate between different Sites, causing
1421 cross-site/cross-process navigations in the browser."""
1422
1423 test_name = "/cross-site"
1424 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001425 return False
1426
1427 params = urllib.unquote(self.path[(len(test_name) + 1):])
1428 slash = params.find('/')
1429 if slash < 0:
1430 self.sendRedirectHelp(test_name)
1431 return True
1432
1433 host = params[:slash]
1434 path = params[(slash+1):]
1435 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1436
1437 self.send_response(301) # moved permanently
1438 self.send_header('Location', dest)
1439 self.send_header('Content-Type', 'text/html')
1440 self.end_headers()
1441 self.wfile.write('<html><head>')
1442 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1443
1444 return True
1445
initial.commit94958cf2008-07-26 22:42:52 +00001446 def ClientRedirectHandler(self):
1447 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001448 '/client-redirect?http://foo.bar/asdf' to redirect to
1449 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001450
1451 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001452 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001453 return False
1454
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001455 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001456 if query_char < 0 or len(self.path) <= query_char + 1:
1457 self.sendRedirectHelp(test_name)
1458 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001459 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001460
1461 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001462 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001463 self.end_headers()
1464 self.wfile.write('<html><head>')
1465 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1466 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1467
1468 return True
1469
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001470 def GetSSLSessionCacheHandler(self):
1471 """Send a reply containing a log of the session cache operations."""
1472
1473 if not self._ShouldHandleRequest('/ssl-session-cache'):
1474 return False
1475
1476 self.send_response(200)
1477 self.send_header('Content-Type', 'text/plain')
1478 self.end_headers()
1479 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001480 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001481 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001482 self.wfile.write('Pass --https-record-resume in order to use' +
1483 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001484 return True
1485
1486 for (action, sessionID) in log:
1487 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001488 return True
1489
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001490 def SSLManySmallRecords(self):
1491 """Sends a reply consisting of a variety of small writes. These will be
1492 translated into a series of small SSL records when used over an HTTPS
1493 server."""
1494
1495 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1496 return False
1497
1498 self.send_response(200)
1499 self.send_header('Content-Type', 'text/plain')
1500 self.end_headers()
1501
1502 # Write ~26K of data, in 1350 byte chunks
1503 for i in xrange(20):
1504 self.wfile.write('*' * 1350)
1505 self.wfile.flush()
1506 return True
1507
agl@chromium.org04700be2013-03-02 18:40:41 +00001508 def GetChannelID(self):
1509 """Send a reply containing the hashed ChannelID that the client provided."""
1510
1511 if not self._ShouldHandleRequest('/channel-id'):
1512 return False
1513
1514 self.send_response(200)
1515 self.send_header('Content-Type', 'text/plain')
1516 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001517 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001518 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1519 return True
1520
nharper08eae822016-01-25 15:54:14 -08001521 def GetTokenBindingEKM(self):
1522 """Send a reply containing the EKM value for token binding from the TLS
1523 layer."""
1524
1525 if not self._ShouldHandleRequest('/tokbind-ekm'):
1526 return False
1527
1528 ekm = self.server.tlsConnection.exportKeyingMaterial(
1529 "EXPORTER-Token-Binding", "", False, 32)
1530 self.send_response(200)
1531 self.send_header('Content-Type', 'application/octet-stream')
1532 self.end_headers()
1533 self.wfile.write(ekm)
1534 return True
1535
nharpercb1adc32016-03-30 16:05:48 -07001536 def ForwardTokenBindingHeader(self):
1537 """Send a redirect that sets the Include-Referer-Token-Binding-ID
1538 header."""
1539
1540 test_name = '/forward-tokbind'
1541 if not self._ShouldHandleRequest(test_name):
1542 return False
1543
1544 query_char = self.path.find('?')
1545 if query_char < 0 or len(self.path) <= query_char + 1:
1546 self.sendRedirectHelp(test_name)
1547 return True
1548 dest = urllib.unquote(self.path[query_char + 1:])
1549
1550 self.send_response(302)
1551 self.send_header('Location', dest)
1552 self.send_header('Include-Referer-Token-Binding-ID', 'true')
1553 self.end_headers()
1554 return True
1555
pneubeckfd4f0442015-08-07 04:55:10 -07001556 def GetClientCert(self):
1557 """Send a reply whether a client certificate was provided."""
1558
1559 if not self._ShouldHandleRequest('/client-cert'):
1560 return False
1561
1562 self.send_response(200)
1563 self.send_header('Content-Type', 'text/plain')
1564 self.end_headers()
1565
1566 cert_chain = self.server.tlsConnection.session.clientCertChain
1567 if cert_chain != None:
1568 self.wfile.write('got client cert with fingerprint: ' +
1569 cert_chain.getFingerprint())
1570 else:
1571 self.wfile.write('got no client cert')
1572 return True
1573
davidben599e7e72014-09-03 16:19:09 -07001574 def ClientCipherListHandler(self):
1575 """Send a reply containing the cipher suite list that the client
1576 provided. Each cipher suite value is serialized in decimal, followed by a
1577 newline."""
1578
1579 if not self._ShouldHandleRequest('/client-cipher-list'):
1580 return False
1581
1582 self.send_response(200)
1583 self.send_header('Content-Type', 'text/plain')
1584 self.end_headers()
1585
davidben11682512014-10-06 21:09:11 -07001586 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1587 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001588 return True
1589
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001590 def CloseSocketHandler(self):
1591 """Closes the socket without sending anything."""
1592
1593 if not self._ShouldHandleRequest('/close-socket'):
1594 return False
1595
1596 self.wfile.close()
1597 return True
1598
initial.commit94958cf2008-07-26 22:42:52 +00001599 def DefaultResponseHandler(self):
1600 """This is the catch-all response handler for requests that aren't handled
1601 by one of the special handlers above.
1602 Note that we specify the content-length as without it the https connection
1603 is not closed properly (and the browser keeps expecting data)."""
1604
1605 contents = "Default response given for path: " + self.path
1606 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001607 self.send_header('Content-Type', 'text/html')
1608 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001609 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001610 if (self.command != 'HEAD'):
1611 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001612 return True
1613
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001614 def RedirectConnectHandler(self):
1615 """Sends a redirect to the CONNECT request for www.redirect.com. This
1616 response is not specified by the RFC, so the browser should not follow
1617 the redirect."""
1618
1619 if (self.path.find("www.redirect.com") < 0):
1620 return False
1621
1622 dest = "http://www.destination.com/foo.js"
1623
1624 self.send_response(302) # moved temporarily
1625 self.send_header('Location', dest)
1626 self.send_header('Connection', 'close')
1627 self.end_headers()
1628 return True
1629
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001630 def ServerAuthConnectHandler(self):
1631 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1632 response doesn't make sense because the proxy server cannot request
1633 server authentication."""
1634
1635 if (self.path.find("www.server-auth.com") < 0):
1636 return False
1637
1638 challenge = 'Basic realm="WallyWorld"'
1639
1640 self.send_response(401) # unauthorized
1641 self.send_header('WWW-Authenticate', challenge)
1642 self.send_header('Connection', 'close')
1643 self.end_headers()
1644 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001645
1646 def DefaultConnectResponseHandler(self):
1647 """This is the catch-all response handler for CONNECT requests that aren't
1648 handled by one of the special handlers above. Real Web servers respond
1649 with 400 to CONNECT requests."""
1650
1651 contents = "Your client has issued a malformed or illegal request."
1652 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001653 self.send_header('Content-Type', 'text/html')
1654 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001655 self.end_headers()
1656 self.wfile.write(contents)
1657 return True
1658
initial.commit94958cf2008-07-26 22:42:52 +00001659 # called by the redirect handling function when there is no parameter
1660 def sendRedirectHelp(self, redirect_name):
1661 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001662 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001663 self.end_headers()
1664 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1665 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1666 self.wfile.write('</body></html>')
1667
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001668 # called by chunked handling function
1669 def sendChunkHelp(self, chunk):
1670 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1671 self.wfile.write('%X\r\n' % len(chunk))
1672 self.wfile.write(chunk)
1673 self.wfile.write('\r\n')
1674
akalin@chromium.org154bb132010-11-12 02:20:27 +00001675
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001676class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001677 def __init__(self, request, client_address, socket_server):
1678 handlers = [self.OCSPResponse]
1679 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001680 testserver_base.BasePageHandler.__init__(self, request, client_address,
1681 socket_server, [], handlers, [],
1682 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001683
1684 def OCSPResponse(self):
1685 self.send_response(200)
1686 self.send_header('Content-Type', 'application/ocsp-response')
1687 self.send_header('Content-Length', str(len(self.ocsp_response)))
1688 self.end_headers()
1689
1690 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001691
mattm@chromium.org830a3712012-11-07 23:00:07 +00001692
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001693class TCPEchoHandler(SocketServer.BaseRequestHandler):
1694 """The RequestHandler class for TCP echo server.
1695
1696 It is instantiated once per connection to the server, and overrides the
1697 handle() method to implement communication to the client.
1698 """
1699
1700 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001701 """Handles the request from the client and constructs a response."""
1702
1703 data = self.request.recv(65536).strip()
1704 # Verify the "echo request" message received from the client. Send back
1705 # "echo response" message if "echo request" message is valid.
1706 try:
1707 return_data = echo_message.GetEchoResponseData(data)
1708 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001709 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001710 except ValueError:
1711 return
1712
1713 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001714
1715
1716class UDPEchoHandler(SocketServer.BaseRequestHandler):
1717 """The RequestHandler class for UDP 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
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001726 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001727 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001728 # Verify the "echo request" message received from the client. Send back
1729 # "echo response" message if "echo request" message is valid.
1730 try:
1731 return_data = echo_message.GetEchoResponseData(data)
1732 if not return_data:
1733 return
1734 except ValueError:
1735 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001736 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001737
1738
bashi@chromium.org33233532012-09-08 17:37:24 +00001739class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1740 """A request handler that behaves as a proxy server which requires
1741 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1742 """
1743
1744 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1745
1746 def parse_request(self):
1747 """Overrides parse_request to check credential."""
1748
1749 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1750 return False
1751
1752 auth = self.headers.getheader('Proxy-Authorization')
1753 if auth != self._AUTH_CREDENTIAL:
1754 self.send_response(407)
1755 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1756 self.end_headers()
1757 return False
1758
1759 return True
1760
1761 def _start_read_write(self, sock):
1762 sock.setblocking(0)
1763 self.request.setblocking(0)
1764 rlist = [self.request, sock]
1765 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001766 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001767 if errors:
1768 self.send_response(500)
1769 self.end_headers()
1770 return
1771 for s in ready_sockets:
1772 received = s.recv(1024)
1773 if len(received) == 0:
1774 return
1775 if s == self.request:
1776 other = sock
1777 else:
1778 other = self.request
1779 other.send(received)
1780
1781 def _do_common_method(self):
1782 url = urlparse.urlparse(self.path)
1783 port = url.port
1784 if not port:
1785 if url.scheme == 'http':
1786 port = 80
1787 elif url.scheme == 'https':
1788 port = 443
1789 if not url.hostname or not port:
1790 self.send_response(400)
1791 self.end_headers()
1792 return
1793
1794 if len(url.path) == 0:
1795 path = '/'
1796 else:
1797 path = url.path
1798 if len(url.query) > 0:
1799 path = '%s?%s' % (url.path, url.query)
1800
1801 sock = None
1802 try:
1803 sock = socket.create_connection((url.hostname, port))
1804 sock.send('%s %s %s\r\n' % (
1805 self.command, path, self.protocol_version))
1806 for header in self.headers.headers:
1807 header = header.strip()
1808 if (header.lower().startswith('connection') or
1809 header.lower().startswith('proxy')):
1810 continue
1811 sock.send('%s\r\n' % header)
1812 sock.send('\r\n')
1813 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001814 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001815 self.send_response(500)
1816 self.end_headers()
1817 finally:
1818 if sock is not None:
1819 sock.close()
1820
1821 def do_CONNECT(self):
1822 try:
1823 pos = self.path.rfind(':')
1824 host = self.path[:pos]
1825 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001826 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001827 self.send_response(400)
1828 self.end_headers()
1829
1830 try:
1831 sock = socket.create_connection((host, port))
1832 self.send_response(200, 'Connection established')
1833 self.end_headers()
1834 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001835 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001836 self.send_response(500)
1837 self.end_headers()
1838 finally:
1839 sock.close()
1840
1841 def do_GET(self):
1842 self._do_common_method()
1843
1844 def do_HEAD(self):
1845 self._do_common_method()
1846
1847
mattm@chromium.org830a3712012-11-07 23:00:07 +00001848class ServerRunner(testserver_base.TestServerRunner):
1849 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001850
mattm@chromium.org830a3712012-11-07 23:00:07 +00001851 def __init__(self):
1852 super(ServerRunner, self).__init__()
1853 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001854
mattm@chromium.org830a3712012-11-07 23:00:07 +00001855 def __make_data_dir(self):
1856 if self.options.data_dir:
1857 if not os.path.isdir(self.options.data_dir):
1858 raise testserver_base.OptionError('specified data dir not found: ' +
1859 self.options.data_dir + ' exiting...')
1860 my_data_dir = self.options.data_dir
1861 else:
1862 # Create the default path to our data dir, relative to the exe dir.
1863 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1864 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001865
mattm@chromium.org830a3712012-11-07 23:00:07 +00001866 #TODO(ibrar): Must use Find* funtion defined in google\tools
1867 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001868
mattm@chromium.org830a3712012-11-07 23:00:07 +00001869 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001870
mattm@chromium.org830a3712012-11-07 23:00:07 +00001871 def create_server(self, server_data):
1872 port = self.options.port
1873 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001874
estark21667d62015-04-08 21:00:16 -07001875 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1876 # will result in a call to |getaddrinfo|, which fails with "nodename
1877 # nor servname provided" for localhost:0 on 10.6.
1878 if self.options.server_type == SERVER_WEBSOCKET and \
1879 host == "localhost" and \
1880 port == 0:
1881 host = "127.0.0.1"
1882
mattm@chromium.org830a3712012-11-07 23:00:07 +00001883 if self.options.server_type == SERVER_HTTP:
1884 if self.options.https:
1885 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001886 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001887 if self.options.cert_and_key_file:
1888 if not os.path.isfile(self.options.cert_and_key_file):
1889 raise testserver_base.OptionError(
1890 'specified server cert file not found: ' +
1891 self.options.cert_and_key_file + ' exiting...')
1892 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001893 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001894 # generate a new certificate and run an OCSP server for it.
1895 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001896 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001897 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001898
mattm@chromium.org830a3712012-11-07 23:00:07 +00001899 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001900
mattm@chromium.org830a3712012-11-07 23:00:07 +00001901 if self.options.ocsp == 'ok':
1902 ocsp_state = minica.OCSP_STATE_GOOD
1903 elif self.options.ocsp == 'revoked':
1904 ocsp_state = minica.OCSP_STATE_REVOKED
1905 elif self.options.ocsp == 'invalid':
1906 ocsp_state = minica.OCSP_STATE_INVALID
1907 elif self.options.ocsp == 'unauthorized':
1908 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1909 elif self.options.ocsp == 'unknown':
1910 ocsp_state = minica.OCSP_STATE_UNKNOWN
1911 else:
1912 raise testserver_base.OptionError('unknown OCSP status: ' +
1913 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001914
mattm@chromium.org830a3712012-11-07 23:00:07 +00001915 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1916 subject = "127.0.0.1",
1917 ocsp_url = ("http://%s:%d/ocsp" %
1918 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001919 ocsp_state = ocsp_state,
1920 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001921
davidben3e2564a2014-11-07 18:51:00 -08001922 if self.options.ocsp_server_unavailable:
1923 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
1924 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
1925 else:
1926 self.__ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org830a3712012-11-07 23:00:07 +00001927
1928 for ca_cert in self.options.ssl_client_ca:
1929 if not os.path.isfile(ca_cert):
1930 raise testserver_base.OptionError(
1931 'specified trusted client CA file not found: ' + ca_cert +
1932 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001933
1934 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08001935 if self.options.staple_ocsp_response:
1936 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001937
mattm@chromium.org830a3712012-11-07 23:00:07 +00001938 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1939 self.options.ssl_client_auth,
1940 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00001941 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00001943 self.options.ssl_key_exchange,
bnc609ad4c2015-10-02 05:11:24 -07001944 self.options.npn_protocols,
mattm@chromium.org830a3712012-11-07 23:00:07 +00001945 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00001946 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00001947 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001948 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00001949 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001950 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07001951 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07001952 self.options.alert_after_handshake,
1953 self.options.disable_channel_id,
1954 self.options.disable_extended_master_secret,
1955 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001956 print 'HTTPS server started on https://%s:%d...' % \
1957 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001958 else:
1959 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001960 print 'HTTP server started on http://%s:%d...' % \
1961 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001962
1963 server.data_dir = self.__make_data_dir()
1964 server.file_root_url = self.options.file_root_url
1965 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00001966 elif self.options.server_type == SERVER_WEBSOCKET:
1967 # Launch pywebsocket via WebSocketServer.
1968 logger = logging.getLogger()
1969 logger.addHandler(logging.StreamHandler())
1970 # TODO(toyoshim): Remove following os.chdir. Currently this operation
1971 # is required to work correctly. It should be fixed from pywebsocket side.
1972 os.chdir(self.__make_data_dir())
1973 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00001974 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00001976 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 websocket_options.use_tls = True
1978 websocket_options.private_key = self.options.cert_and_key_file
1979 websocket_options.certificate = self.options.cert_and_key_file
1980 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00001981 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00001982 websocket_options.tls_client_auth = True
1983 if len(self.options.ssl_client_ca) != 1:
1984 raise testserver_base.OptionError(
1985 'one trusted client CA file should be specified')
1986 if not os.path.isfile(self.options.ssl_client_ca[0]):
1987 raise testserver_base.OptionError(
1988 'specified trusted client CA file not found: ' +
1989 self.options.ssl_client_ca[0] + ' exiting...')
1990 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07001991 print 'Trying to start websocket server on %s://%s:%d...' % \
1992 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001993 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00001994 print 'WebSocket server started on %s://%s:%d...' % \
1995 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001996 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00001997 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00001998 elif self.options.server_type == SERVER_TCP_ECHO:
1999 # Used for generating the key (randomly) that encodes the "echo request"
2000 # message.
2001 random.seed()
2002 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002003 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004 server_data['port'] = server.server_port
2005 elif self.options.server_type == SERVER_UDP_ECHO:
2006 # Used for generating the key (randomly) that encodes the "echo request"
2007 # message.
2008 random.seed()
2009 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002010 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002011 server_data['port'] = server.server_port
2012 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2013 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002014 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002015 server_data['port'] = server.server_port
2016 elif self.options.server_type == SERVER_FTP:
2017 my_data_dir = self.__make_data_dir()
2018
2019 # Instantiate a dummy authorizer for managing 'virtual' users
2020 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2021
xleng9d4c45f2015-05-04 16:26:12 -07002022 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002023 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2024
xleng9d4c45f2015-05-04 16:26:12 -07002025 # Define a read-only anonymous user unless disabled
2026 if not self.options.no_anonymous_ftp_user:
2027 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002028
2029 # Instantiate FTP handler class
2030 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2031 ftp_handler.authorizer = authorizer
2032
2033 # Define a customized banner (string returned when client connects)
2034 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2035 pyftpdlib.ftpserver.__ver__)
2036
2037 # Instantiate FTP server class and listen to address:port
2038 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2039 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002040 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002041 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002042 raise testserver_base.OptionError('unknown server type' +
2043 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002044
mattm@chromium.org830a3712012-11-07 23:00:07 +00002045 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002046
mattm@chromium.org830a3712012-11-07 23:00:07 +00002047 def run_server(self):
2048 if self.__ocsp_server:
2049 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002050
mattm@chromium.org830a3712012-11-07 23:00:07 +00002051 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002052
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 if self.__ocsp_server:
2054 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002055
mattm@chromium.org830a3712012-11-07 23:00:07 +00002056 def add_options(self):
2057 testserver_base.TestServerRunner.add_options(self)
2058 self.option_parser.add_option('-f', '--ftp', action='store_const',
2059 const=SERVER_FTP, default=SERVER_HTTP,
2060 dest='server_type',
2061 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002062 self.option_parser.add_option('--tcp-echo', action='store_const',
2063 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2064 dest='server_type',
2065 help='start up a tcp echo server.')
2066 self.option_parser.add_option('--udp-echo', action='store_const',
2067 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2068 dest='server_type',
2069 help='start up a udp echo server.')
2070 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2071 const=SERVER_BASIC_AUTH_PROXY,
2072 default=SERVER_HTTP, dest='server_type',
2073 help='start up a proxy server which requires '
2074 'basic authentication.')
2075 self.option_parser.add_option('--websocket', action='store_const',
2076 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2077 dest='server_type',
2078 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002079 self.option_parser.add_option('--https', action='store_true',
2080 dest='https', help='Specify that https '
2081 'should be used.')
2082 self.option_parser.add_option('--cert-and-key-file',
2083 dest='cert_and_key_file', help='specify the '
2084 'path to the file containing the certificate '
2085 'and private key for the server in PEM '
2086 'format')
2087 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2088 help='The type of OCSP response generated '
2089 'for the automatically generated '
2090 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002091 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2092 default=0, type=int,
2093 help='If non-zero then the generated '
2094 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2096 default='0', type='int',
2097 help='If nonzero, certain TLS connections '
2098 'will be aborted in order to test version '
2099 'fallback. 1 means all TLS versions will be '
2100 'aborted. 2 means TLS 1.1 or higher will be '
2101 'aborted. 3 means TLS 1.2 or higher will be '
2102 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002103 self.option_parser.add_option('--tls-intolerance-type',
2104 dest='tls_intolerance_type',
2105 default="alert",
2106 help='Controls how the server reacts to a '
2107 'TLS version it is intolerant to. Valid '
2108 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002109 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2110 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002111 default='',
2112 help='Base64 encoded SCT list. If set, '
2113 'server will respond with a '
2114 'signed_certificate_timestamp TLS extension '
2115 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002116 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2117 default=False, const=True,
2118 action='store_const',
2119 help='If given, TLS_FALLBACK_SCSV support '
2120 'will be enabled. This causes the server to '
2121 'reject fallback connections from compatible '
2122 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002123 self.option_parser.add_option('--staple-ocsp-response',
2124 dest='staple_ocsp_response',
2125 default=False, action='store_true',
2126 help='If set, server will staple the OCSP '
2127 'response whenever OCSP is on and the client '
2128 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002129 self.option_parser.add_option('--https-record-resume',
2130 dest='record_resume', const=True,
2131 default=False, action='store_const',
2132 help='Record resumption cache events rather '
2133 'than resuming as normal. Allows the use of '
2134 'the /ssl-session-cache request')
2135 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2136 help='Require SSL client auth on every '
2137 'connection.')
2138 self.option_parser.add_option('--ssl-client-ca', action='append',
2139 default=[], help='Specify that the client '
2140 'certificate request should include the CA '
2141 'named in the subject of the DER-encoded '
2142 'certificate contained in the specified '
2143 'file. This option may appear multiple '
2144 'times, indicating multiple CA names should '
2145 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002146 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2147 default=[], help='Specify that the client '
2148 'certificate request should include the '
2149 'specified certificate_type value. This '
2150 'option may appear multiple times, '
2151 'indicating multiple values should be send '
2152 'in the request. Valid values are '
2153 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2154 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002155 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2156 help='Specify the bulk encryption '
2157 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002158 'SSL server. Valid values are "aes128gcm", '
2159 '"aes256", "aes128", "3des", "rc4". If '
2160 'omitted, all algorithms will be used. This '
2161 'option may appear multiple times, '
2162 'indicating multiple algorithms should be '
2163 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002164 self.option_parser.add_option('--ssl-key-exchange', action='append',
2165 help='Specify the key exchange algorithm(s)'
2166 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002167 'Valid values are "rsa", "dhe_rsa", '
2168 '"ecdhe_rsa". If omitted, all algorithms '
2169 'will be used. This option may appear '
2170 'multiple times, indicating multiple '
2171 'algorithms should be enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002172 # TODO(davidben): Add ALPN support to tlslite.
bnc609ad4c2015-10-02 05:11:24 -07002173 self.option_parser.add_option('--npn-protocols', action='append',
2174 help='Specify the list of protocols sent in'
2175 'an NPN response. The server will not'
2176 'support NPN if the list is empty.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002177 self.option_parser.add_option('--file-root-url', default='/files/',
2178 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002179 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2180 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2181 dest='ws_basic_auth',
2182 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002183 self.option_parser.add_option('--ocsp-server-unavailable',
2184 dest='ocsp_server_unavailable',
2185 default=False, action='store_true',
2186 help='If set, the OCSP server will return '
2187 'a tryLater status rather than the actual '
2188 'OCSP response.')
davidben21cda342015-03-17 18:04:28 -07002189 self.option_parser.add_option('--alert-after-handshake',
2190 dest='alert_after_handshake',
2191 default=False, action='store_true',
2192 help='If set, the server will send a fatal '
2193 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002194 self.option_parser.add_option('--no-anonymous-ftp-user',
2195 dest='no_anonymous_ftp_user',
2196 default=False, action='store_true',
2197 help='If set, the FTP server will not create '
2198 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002199 self.option_parser.add_option('--disable-channel-id', action='store_true')
2200 self.option_parser.add_option('--disable-extended-master-secret',
2201 action='store_true')
2202 self.option_parser.add_option('--token-binding-params', action='append',
2203 default=[], type='int')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002204
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002205
initial.commit94958cf2008-07-26 22:42:52 +00002206if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002207 sys.exit(ServerRunner().main())