blob: 99684c8e3079aad014ca97fe47c3194632faddb7 [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,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000157 ssl_bulk_ciphers, ssl_key_exchanges, enable_npn,
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 = []
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000175 if enable_npn:
176 self.next_protos = ['http/1.1']
177 else:
178 self.next_protos = None
ekasper@google.com24aa8222013-11-28 13:43:26 +0000179 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000180 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000181 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000182
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000183 if ssl_client_auth:
184 for ca_file in ssl_client_cas:
185 s = open(ca_file).read()
186 x509 = tlslite.api.X509()
187 x509.parse(s)
188 self.ssl_client_cas.append(x509.subject)
189
190 for cert_type in ssl_client_cert_types:
191 self.ssl_client_cert_types.append({
192 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000193 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
194 }[cert_type])
195
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000196 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800197 # Enable SSLv3 for testing purposes.
198 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000199 if ssl_bulk_ciphers is not None:
200 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000201 if ssl_key_exchanges is not None:
202 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000203 if tls_intolerant != 0:
204 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
205 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700206 if alert_after_handshake:
207 self.ssl_handshake_settings.alertAfterHandshake = True
nharper1e8bf4b2015-09-18 12:23:02 -0700208 if disable_channel_id:
209 self.ssl_handshake_settings.enableChannelID = False
210 if disable_ems:
211 self.ssl_handshake_settings.enableExtendedMasterSecret = False
212 self.ssl_handshake_settings.supportedTokenBindingParams = \
213 token_binding_params
initial.commit94958cf2008-07-26 22:42:52 +0000214
rsleevi8146efa2015-03-16 12:31:24 -0700215 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000216 # If record_resume_info is true then we'll replace the session cache with
217 # an object that records the lookups and inserts that it sees.
218 self.session_cache = RecordingSSLSessionCache()
219 else:
220 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000221 testserver_base.StoppableHTTPServer.__init__(self,
222 server_address,
223 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000224
225 def handshake(self, tlsConnection):
226 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000227
initial.commit94958cf2008-07-26 22:42:52 +0000228 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000229 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000230 tlsConnection.handshakeServer(certChain=self.cert_chain,
231 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000232 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000233 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000234 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000235 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000236 reqCertTypes=self.ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000237 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000238 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000239 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000240 fallbackSCSV=self.fallback_scsv_enabled,
241 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000242 tlsConnection.ignoreAbruptClose = True
243 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000244 except tlslite.api.TLSAbruptCloseError:
245 # Ignore abrupt close.
246 return True
initial.commit94958cf2008-07-26 22:42:52 +0000247 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000248 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000249 return False
250
akalin@chromium.org154bb132010-11-12 02:20:27 +0000251
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000252class FTPServer(testserver_base.ClientRestrictingServerMixIn,
253 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000254 """This is a specialization of FTPServer that adds client verification."""
255
256 pass
257
258
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000259class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
260 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000261 """A TCP echo server that echoes back what it has received."""
262
263 def server_bind(self):
264 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000265
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000266 SocketServer.TCPServer.server_bind(self)
267 host, port = self.socket.getsockname()[:2]
268 self.server_name = socket.getfqdn(host)
269 self.server_port = port
270
271 def serve_forever(self):
272 self.stop = False
273 self.nonce_time = None
274 while not self.stop:
275 self.handle_request()
276 self.socket.close()
277
278
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000279class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
280 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000281 """A UDP echo server that echoes back what it has received."""
282
283 def server_bind(self):
284 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000285
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000286 SocketServer.UDPServer.server_bind(self)
287 host, port = self.socket.getsockname()[:2]
288 self.server_name = socket.getfqdn(host)
289 self.server_port = port
290
291 def serve_forever(self):
292 self.stop = False
293 self.nonce_time = None
294 while not self.stop:
295 self.handle_request()
296 self.socket.close()
297
298
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000299class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000300 # Class variables to allow for persistence state between page handler
301 # invocations
302 rst_limits = {}
303 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000304
305 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000306 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000307 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000308 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000309 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000310 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000311 self.NoCacheMaxAgeTimeHandler,
312 self.NoCacheTimeHandler,
313 self.CacheTimeHandler,
314 self.CacheExpiresHandler,
315 self.CacheProxyRevalidateHandler,
316 self.CachePrivateHandler,
317 self.CachePublicHandler,
318 self.CacheSMaxAgeHandler,
319 self.CacheMustRevalidateHandler,
320 self.CacheMustRevalidateMaxAgeHandler,
321 self.CacheNoStoreHandler,
322 self.CacheNoStoreMaxAgeHandler,
323 self.CacheNoTransformHandler,
324 self.DownloadHandler,
325 self.DownloadFinishHandler,
326 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000327 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000328 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000329 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000331 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000332 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000333 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000334 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.AuthBasicHandler,
336 self.AuthDigestHandler,
337 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000338 self.ChunkedServerHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000339 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000340 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700341 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000343 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000344 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000345 self.GetChannelID,
pneubeckfd4f0442015-08-07 04:55:10 -0700346 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700347 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000348 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000349 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000350 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000351 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000352 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000353 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000354 self.PostOnlyFileHandler,
355 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000356 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000357 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000358 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000359 head_handlers = [
360 self.FileHandler,
361 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000362
maruel@google.come250a9b2009-03-10 17:39:46 +0000363 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000364 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000365 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 'gif': 'image/gif',
367 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000368 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700369 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000370 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000371 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000372 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000373 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000374 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000375 }
initial.commit94958cf2008-07-26 22:42:52 +0000376 self._default_mime_type = 'text/html'
377
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000378 testserver_base.BasePageHandler.__init__(self, request, client_address,
379 socket_server, connect_handlers,
380 get_handlers, head_handlers,
381 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000382
initial.commit94958cf2008-07-26 22:42:52 +0000383 def GetMIMETypeFromName(self, file_name):
384 """Returns the mime type for the specified file_name. So far it only looks
385 at the file extension."""
386
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000387 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000388 if len(extension) == 0:
389 # no extension.
390 return self._default_mime_type
391
ericroman@google.comc17ca532009-05-07 03:51:05 +0000392 # extension starts with a dot, so we need to remove it
393 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000394
initial.commit94958cf2008-07-26 22:42:52 +0000395 def NoCacheMaxAgeTimeHandler(self):
396 """This request handler yields a page with the title set to the current
397 system time, and no caching requested."""
398
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000399 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000400 return False
401
402 self.send_response(200)
403 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000404 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000405 self.end_headers()
406
maruel@google.come250a9b2009-03-10 17:39:46 +0000407 self.wfile.write('<html><head><title>%s</title></head></html>' %
408 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000409
410 return True
411
412 def NoCacheTimeHandler(self):
413 """This request handler yields a page with the title set to the current
414 system time, and no caching requested."""
415
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000416 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000417 return False
418
419 self.send_response(200)
420 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000421 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000422 self.end_headers()
423
maruel@google.come250a9b2009-03-10 17:39:46 +0000424 self.wfile.write('<html><head><title>%s</title></head></html>' %
425 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000426
427 return True
428
429 def CacheTimeHandler(self):
430 """This request handler yields a page with the title set to the current
431 system time, and allows caching for one minute."""
432
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000433 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000434 return False
435
436 self.send_response(200)
437 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000438 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000439 self.end_headers()
440
maruel@google.come250a9b2009-03-10 17:39:46 +0000441 self.wfile.write('<html><head><title>%s</title></head></html>' %
442 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000443
444 return True
445
446 def CacheExpiresHandler(self):
447 """This request handler yields a page with the title set to the current
448 system time, and set the page to expire on 1 Jan 2099."""
449
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000450 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000451 return False
452
453 self.send_response(200)
454 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000455 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000456 self.end_headers()
457
maruel@google.come250a9b2009-03-10 17:39:46 +0000458 self.wfile.write('<html><head><title>%s</title></head></html>' %
459 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000460
461 return True
462
463 def CacheProxyRevalidateHandler(self):
464 """This request handler yields a page with the title set to the current
465 system time, and allows caching for 60 seconds"""
466
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000467 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000468 return False
469
470 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000471 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000472 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
473 self.end_headers()
474
maruel@google.come250a9b2009-03-10 17:39:46 +0000475 self.wfile.write('<html><head><title>%s</title></head></html>' %
476 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000477
478 return True
479
480 def CachePrivateHandler(self):
481 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700482 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000483
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000484 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000485 return False
486
487 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000488 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000489 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000490 self.end_headers()
491
maruel@google.come250a9b2009-03-10 17:39:46 +0000492 self.wfile.write('<html><head><title>%s</title></head></html>' %
493 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000494
495 return True
496
497 def CachePublicHandler(self):
498 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700499 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000500
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000501 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000502 return False
503
504 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000505 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000506 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000507 self.end_headers()
508
maruel@google.come250a9b2009-03-10 17:39:46 +0000509 self.wfile.write('<html><head><title>%s</title></head></html>' %
510 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000511
512 return True
513
514 def CacheSMaxAgeHandler(self):
515 """This request handler yields a page with the title set to the current
516 system time, and does not allow for caching."""
517
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000518 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000519 return False
520
521 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000522 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000523 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
524 self.end_headers()
525
maruel@google.come250a9b2009-03-10 17:39:46 +0000526 self.wfile.write('<html><head><title>%s</title></head></html>' %
527 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000528
529 return True
530
531 def CacheMustRevalidateHandler(self):
532 """This request handler yields a page with the title set to the current
533 system time, and does not allow caching."""
534
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000535 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000536 return False
537
538 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000539 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000540 self.send_header('Cache-Control', 'must-revalidate')
541 self.end_headers()
542
maruel@google.come250a9b2009-03-10 17:39:46 +0000543 self.wfile.write('<html><head><title>%s</title></head></html>' %
544 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000545
546 return True
547
548 def CacheMustRevalidateMaxAgeHandler(self):
549 """This request handler yields a page with the title set to the current
550 system time, and does not allow caching event though max-age of 60
551 seconds is specified."""
552
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000553 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000554 return False
555
556 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000557 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000558 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
559 self.end_headers()
560
maruel@google.come250a9b2009-03-10 17:39:46 +0000561 self.wfile.write('<html><head><title>%s</title></head></html>' %
562 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000563
564 return True
565
initial.commit94958cf2008-07-26 22:42:52 +0000566 def CacheNoStoreHandler(self):
567 """This request handler yields a page with the title set to the current
568 system time, and does not allow the page to be stored."""
569
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000570 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000571 return False
572
573 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000574 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000575 self.send_header('Cache-Control', 'no-store')
576 self.end_headers()
577
maruel@google.come250a9b2009-03-10 17:39:46 +0000578 self.wfile.write('<html><head><title>%s</title></head></html>' %
579 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000580
581 return True
582
583 def CacheNoStoreMaxAgeHandler(self):
584 """This request handler yields a page with the title set to the current
585 system time, and does not allow the page to be stored even though max-age
586 of 60 seconds is specified."""
587
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000588 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000589 return False
590
591 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000592 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000593 self.send_header('Cache-Control', 'max-age=60, no-store')
594 self.end_headers()
595
maruel@google.come250a9b2009-03-10 17:39:46 +0000596 self.wfile.write('<html><head><title>%s</title></head></html>' %
597 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000598
599 return True
600
601
602 def CacheNoTransformHandler(self):
603 """This request handler yields a page with the title set to the current
604 system time, and does not allow the content to transformed during
605 user-agent caching"""
606
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000607 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000608 return False
609
610 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000611 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000612 self.send_header('Cache-Control', 'no-transform')
613 self.end_headers()
614
maruel@google.come250a9b2009-03-10 17:39:46 +0000615 self.wfile.write('<html><head><title>%s</title></head></html>' %
616 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000617
618 return True
619
620 def EchoHeader(self):
621 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000622
ananta@chromium.org219b2062009-10-23 16:09:41 +0000623 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000624
ananta@chromium.org56812d02011-04-07 17:52:05 +0000625 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000626 """This function echoes back the value of a specific request header while
627 allowing caching for 16 hours."""
628
ananta@chromium.org56812d02011-04-07 17:52:05 +0000629 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000630
631 def EchoHeaderHelper(self, echo_header):
632 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000633
ananta@chromium.org219b2062009-10-23 16:09:41 +0000634 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000635 return False
636
637 query_char = self.path.find('?')
638 if query_char != -1:
639 header_name = self.path[query_char+1:]
640
641 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000642 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000643 if echo_header == '/echoheadercache':
644 self.send_header('Cache-control', 'max-age=60000')
645 else:
646 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000647 # insert a vary header to properly indicate that the cachability of this
648 # request is subject to value of the request header being echoed.
649 if len(header_name) > 0:
650 self.send_header('Vary', header_name)
651 self.end_headers()
652
653 if len(header_name) > 0:
654 self.wfile.write(self.headers.getheader(header_name))
655
656 return True
657
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000658 def ReadRequestBody(self):
659 """This function reads the body of the current HTTP request, handling
660 both plain and chunked transfer encoded requests."""
661
662 if self.headers.getheader('transfer-encoding') != 'chunked':
663 length = int(self.headers.getheader('content-length'))
664 return self.rfile.read(length)
665
666 # Read the request body as chunks.
667 body = ""
668 while True:
669 line = self.rfile.readline()
670 length = int(line, 16)
671 if length == 0:
672 self.rfile.readline()
673 break
674 body += self.rfile.read(length)
675 self.rfile.read(2)
676 return body
677
initial.commit94958cf2008-07-26 22:42:52 +0000678 def EchoHandler(self):
679 """This handler just echoes back the payload of the request, for testing
680 form submission."""
681
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000682 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000683 return False
684
hirono2838c572015-01-21 12:18:11 -0800685 _, _, _, _, query, _ = urlparse.urlparse(self.path)
686 query_params = cgi.parse_qs(query, True)
687 if 'status' in query_params:
688 self.send_response(int(query_params['status'][0]))
689 else:
690 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000691 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000692 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000693 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000694 return True
695
696 def EchoTitleHandler(self):
697 """This handler is like Echo, but sets the page title to the request."""
698
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000699 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000700 return False
701
702 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000703 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000704 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000705 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000706 self.wfile.write('<html><head><title>')
707 self.wfile.write(request)
708 self.wfile.write('</title></head></html>')
709 return True
710
711 def EchoAllHandler(self):
712 """This handler yields a (more) human-readable page listing information
713 about the request header & contents."""
714
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000715 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000716 return False
717
718 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000719 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000720 self.end_headers()
721 self.wfile.write('<html><head><style>'
722 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
723 '</style></head><body>'
724 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000725 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000726 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000727
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000728 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000729 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000730 params = cgi.parse_qs(qs, keep_blank_values=1)
731
732 for param in params:
733 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000734
735 self.wfile.write('</pre>')
736
737 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
738
739 self.wfile.write('</body></html>')
740 return True
741
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000742 def EchoMultipartPostHandler(self):
743 """This handler echoes received multipart post data as json format."""
744
745 if not (self._ShouldHandleRequest("/echomultipartpost") or
746 self._ShouldHandleRequest("/searchbyimage")):
747 return False
748
749 content_type, parameters = cgi.parse_header(
750 self.headers.getheader('content-type'))
751 if content_type == 'multipart/form-data':
752 post_multipart = cgi.parse_multipart(self.rfile, parameters)
753 elif content_type == 'application/x-www-form-urlencoded':
754 raise Exception('POST by application/x-www-form-urlencoded is '
755 'not implemented.')
756 else:
757 post_multipart = {}
758
759 # Since the data can be binary, we encode them by base64.
760 post_multipart_base64_encoded = {}
761 for field, values in post_multipart.items():
762 post_multipart_base64_encoded[field] = [base64.b64encode(value)
763 for value in values]
764
765 result = {'POST_multipart' : post_multipart_base64_encoded}
766
767 self.send_response(200)
768 self.send_header("Content-type", "text/plain")
769 self.end_headers()
770 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
771 return True
772
initial.commit94958cf2008-07-26 22:42:52 +0000773 def DownloadHandler(self):
774 """This handler sends a downloadable file with or without reporting
775 the size (6K)."""
776
777 if self.path.startswith("/download-unknown-size"):
778 send_length = False
779 elif self.path.startswith("/download-known-size"):
780 send_length = True
781 else:
782 return False
783
784 #
785 # The test which uses this functionality is attempting to send
786 # small chunks of data to the client. Use a fairly large buffer
787 # so that we'll fill chrome's IO buffer enough to force it to
788 # actually write the data.
789 # See also the comments in the client-side of this test in
790 # download_uitest.cc
791 #
792 size_chunk1 = 35*1024
793 size_chunk2 = 10*1024
794
795 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000796 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000797 self.send_header('Cache-Control', 'max-age=0')
798 if send_length:
799 self.send_header('Content-Length', size_chunk1 + size_chunk2)
800 self.end_headers()
801
802 # First chunk of data:
803 self.wfile.write("*" * size_chunk1)
804 self.wfile.flush()
805
806 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000807 self.server.wait_for_download = True
808 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000809 self.server.handle_request()
810
811 # Second chunk of data:
812 self.wfile.write("*" * size_chunk2)
813 return True
814
815 def DownloadFinishHandler(self):
816 """This handler just tells the server to finish the current download."""
817
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000818 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000819 return False
820
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000821 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000822 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000823 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000824 self.send_header('Cache-Control', 'max-age=0')
825 self.end_headers()
826 return True
827
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000828 def _ReplaceFileData(self, data, query_parameters):
829 """Replaces matching substrings in a file.
830
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000831 If the 'replace_text' URL query parameter is present, it is expected to be
832 of the form old_text:new_text, which indicates that any old_text strings in
833 the file are replaced with new_text. Multiple 'replace_text' parameters may
834 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000835
836 If the parameters are not present, |data| is returned.
837 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000838
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000839 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000840 replace_text_values = query_dict.get('replace_text', [])
841 for replace_text_value in replace_text_values:
842 replace_text_args = replace_text_value.split(':')
843 if len(replace_text_args) != 2:
844 raise ValueError(
845 'replace_text must be of form old_text:new_text. Actual value: %s' %
846 replace_text_value)
847 old_text_b64, new_text_b64 = replace_text_args
848 old_text = base64.urlsafe_b64decode(old_text_b64)
849 new_text = base64.urlsafe_b64decode(new_text_b64)
850 data = data.replace(old_text, new_text)
851 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000852
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000853 def ZipFileHandler(self):
854 """This handler sends the contents of the requested file in compressed form.
855 Can pass in a parameter that specifies that the content length be
856 C - the compressed size (OK),
857 U - the uncompressed size (Non-standard, but handled),
858 S - less than compressed (OK because we keep going),
859 M - larger than compressed but less than uncompressed (an error),
860 L - larger than uncompressed (an error)
861 Example: compressedfiles/Picture_1.doc?C
862 """
863
864 prefix = "/compressedfiles/"
865 if not self.path.startswith(prefix):
866 return False
867
868 # Consume a request body if present.
869 if self.command == 'POST' or self.command == 'PUT' :
870 self.ReadRequestBody()
871
872 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
873
874 if not query in ('C', 'U', 'S', 'M', 'L'):
875 return False
876
877 sub_path = url_path[len(prefix):]
878 entries = sub_path.split('/')
879 file_path = os.path.join(self.server.data_dir, *entries)
880 if os.path.isdir(file_path):
881 file_path = os.path.join(file_path, 'index.html')
882
883 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000884 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000885 self.send_error(404)
886 return True
887
888 f = open(file_path, "rb")
889 data = f.read()
890 uncompressed_len = len(data)
891 f.close()
892
893 # Compress the data.
894 data = zlib.compress(data)
895 compressed_len = len(data)
896
897 content_length = compressed_len
898 if query == 'U':
899 content_length = uncompressed_len
900 elif query == 'S':
901 content_length = compressed_len / 2
902 elif query == 'M':
903 content_length = (compressed_len + uncompressed_len) / 2
904 elif query == 'L':
905 content_length = compressed_len + uncompressed_len
906
907 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000908 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000909 self.send_header('Content-encoding', 'deflate')
910 self.send_header('Connection', 'close')
911 self.send_header('Content-Length', content_length)
912 self.send_header('ETag', '\'' + file_path + '\'')
913 self.end_headers()
914
915 self.wfile.write(data)
916
917 return True
918
initial.commit94958cf2008-07-26 22:42:52 +0000919 def FileHandler(self):
920 """This handler sends the contents of the requested file. Wow, it's like
921 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000922
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000923 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000924 if not self.path.startswith(prefix):
925 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000926 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000927
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000928 def PostOnlyFileHandler(self):
929 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000930
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000931 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000932 if not self.path.startswith(prefix):
933 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000934 return self._FileHandlerHelper(prefix)
935
936 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000937 request_body = ''
938 if self.command == 'POST' or self.command == 'PUT':
939 # Consume a request body if present.
940 request_body = self.ReadRequestBody()
941
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000942 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000943 query_dict = cgi.parse_qs(query)
944
945 expected_body = query_dict.get('expected_body', [])
946 if expected_body and request_body not in expected_body:
947 self.send_response(404)
948 self.end_headers()
949 self.wfile.write('')
950 return True
951
952 expected_headers = query_dict.get('expected_headers', [])
953 for expected_header in expected_headers:
954 header_name, expected_value = expected_header.split(':')
955 if self.headers.getheader(header_name) != expected_value:
956 self.send_response(404)
957 self.end_headers()
958 self.wfile.write('')
959 return True
960
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000961 sub_path = url_path[len(prefix):]
962 entries = sub_path.split('/')
963 file_path = os.path.join(self.server.data_dir, *entries)
964 if os.path.isdir(file_path):
965 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000966
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000967 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000968 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000969 self.send_error(404)
970 return True
971
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000972 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000973 data = f.read()
974 f.close()
975
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000976 data = self._ReplaceFileData(data, query)
977
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000978 old_protocol_version = self.protocol_version
979
initial.commit94958cf2008-07-26 22:42:52 +0000980 # If file.mock-http-headers exists, it contains the headers we
981 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000982 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000983 if os.path.isfile(headers_path):
984 f = open(headers_path, "r")
985
986 # "HTTP/1.1 200 OK"
987 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000988 http_major, http_minor, status_code = re.findall(
989 'HTTP/(\d+).(\d+) (\d+)', response)[0]
990 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000991 self.send_response(int(status_code))
992
993 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000994 header_values = re.findall('(\S+):\s*(.*)', line)
995 if len(header_values) > 0:
996 # "name: value"
997 name, value = header_values[0]
998 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000999 f.close()
1000 else:
1001 # Could be more generic once we support mime-type sniffing, but for
1002 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +00001003
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001004 range_header = self.headers.get('Range')
1005 if range_header and range_header.startswith('bytes='):
1006 # Note this doesn't handle all valid byte range_header values (i.e.
1007 # left open ended ones), just enough for what we needed so far.
1008 range_header = range_header[6:].split('-')
1009 start = int(range_header[0])
1010 if range_header[1]:
1011 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001012 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001013 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001014
1015 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001016 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1017 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001018 self.send_header('Content-Range', content_range)
1019 data = data[start: end + 1]
1020 else:
1021 self.send_response(200)
1022
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001023 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001024 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001025 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001026 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001027 self.end_headers()
1028
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001029 if (self.command != 'HEAD'):
1030 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001031
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001032 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001033 return True
1034
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001035 def SetCookieHandler(self):
1036 """This handler just sets a cookie, for testing cookie handling."""
1037
1038 if not self._ShouldHandleRequest("/set-cookie"):
1039 return False
1040
1041 query_char = self.path.find('?')
1042 if query_char != -1:
1043 cookie_values = self.path[query_char + 1:].split('&')
1044 else:
1045 cookie_values = ("",)
1046 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001047 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001048 for cookie_value in cookie_values:
1049 self.send_header('Set-Cookie', '%s' % cookie_value)
1050 self.end_headers()
1051 for cookie_value in cookie_values:
1052 self.wfile.write('%s' % cookie_value)
1053 return True
1054
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001055 def SetManyCookiesHandler(self):
1056 """This handler just sets a given number of cookies, for testing handling
1057 of large numbers of cookies."""
1058
1059 if not self._ShouldHandleRequest("/set-many-cookies"):
1060 return False
1061
1062 query_char = self.path.find('?')
1063 if query_char != -1:
1064 num_cookies = int(self.path[query_char + 1:])
1065 else:
1066 num_cookies = 0
1067 self.send_response(200)
1068 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001069 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001070 self.send_header('Set-Cookie', 'a=')
1071 self.end_headers()
1072 self.wfile.write('%d cookies were sent' % num_cookies)
1073 return True
1074
mattm@chromium.org983fc462012-06-30 00:52:08 +00001075 def ExpectAndSetCookieHandler(self):
1076 """Expects some cookies to be sent, and if they are, sets more cookies.
1077
1078 The expect parameter specifies a required cookie. May be specified multiple
1079 times.
1080 The set parameter specifies a cookie to set if all required cookies are
1081 preset. May be specified multiple times.
1082 The data parameter specifies the response body data to be returned."""
1083
1084 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1085 return False
1086
1087 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1088 query_dict = cgi.parse_qs(query)
1089 cookies = set()
1090 if 'Cookie' in self.headers:
1091 cookie_header = self.headers.getheader('Cookie')
1092 cookies.update([s.strip() for s in cookie_header.split(';')])
1093 got_all_expected_cookies = True
1094 for expected_cookie in query_dict.get('expect', []):
1095 if expected_cookie not in cookies:
1096 got_all_expected_cookies = False
1097 self.send_response(200)
1098 self.send_header('Content-Type', 'text/html')
1099 if got_all_expected_cookies:
1100 for cookie_value in query_dict.get('set', []):
1101 self.send_header('Set-Cookie', '%s' % cookie_value)
1102 self.end_headers()
1103 for data_value in query_dict.get('data', []):
1104 self.wfile.write(data_value)
1105 return True
1106
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001107 def SetHeaderHandler(self):
1108 """This handler sets a response header. Parameters are in the
1109 key%3A%20value&key2%3A%20value2 format."""
1110
1111 if not self._ShouldHandleRequest("/set-header"):
1112 return False
1113
1114 query_char = self.path.find('?')
1115 if query_char != -1:
1116 headers_values = self.path[query_char + 1:].split('&')
1117 else:
1118 headers_values = ("",)
1119 self.send_response(200)
1120 self.send_header('Content-Type', 'text/html')
1121 for header_value in headers_values:
1122 header_value = urllib.unquote(header_value)
1123 (key, value) = header_value.split(': ', 1)
1124 self.send_header(key, value)
1125 self.end_headers()
1126 for header_value in headers_values:
1127 self.wfile.write('%s' % header_value)
1128 return True
1129
initial.commit94958cf2008-07-26 22:42:52 +00001130 def AuthBasicHandler(self):
1131 """This handler tests 'Basic' authentication. It just sends a page with
1132 title 'user/pass' if you succeed."""
1133
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001134 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001135 return False
1136
1137 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001138 expected_password = 'secret'
1139 realm = 'testrealm'
1140 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001141
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001142 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1143 query_params = cgi.parse_qs(query, True)
1144 if 'set-cookie-if-challenged' in query_params:
1145 set_cookie_if_challenged = True
1146 if 'password' in query_params:
1147 expected_password = query_params['password'][0]
1148 if 'realm' in query_params:
1149 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001150
initial.commit94958cf2008-07-26 22:42:52 +00001151 auth = self.headers.getheader('authorization')
1152 try:
1153 if not auth:
1154 raise Exception('no auth')
1155 b64str = re.findall(r'Basic (\S+)', auth)[0]
1156 userpass = base64.b64decode(b64str)
1157 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001158 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001159 raise Exception('wrong password')
1160 except Exception, e:
1161 # Authentication failed.
1162 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001163 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001164 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001165 if set_cookie_if_challenged:
1166 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001167 self.end_headers()
1168 self.wfile.write('<html><head>')
1169 self.wfile.write('<title>Denied: %s</title>' % e)
1170 self.wfile.write('</head><body>')
1171 self.wfile.write('auth=%s<p>' % auth)
1172 self.wfile.write('b64str=%s<p>' % b64str)
1173 self.wfile.write('username: %s<p>' % username)
1174 self.wfile.write('userpass: %s<p>' % userpass)
1175 self.wfile.write('password: %s<p>' % password)
1176 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1177 self.wfile.write('</body></html>')
1178 return True
1179
1180 # Authentication successful. (Return a cachable response to allow for
1181 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001182 old_protocol_version = self.protocol_version
1183 self.protocol_version = "HTTP/1.1"
1184
initial.commit94958cf2008-07-26 22:42:52 +00001185 if_none_match = self.headers.getheader('if-none-match')
1186 if if_none_match == "abc":
1187 self.send_response(304)
1188 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001189 elif url_path.endswith(".gif"):
1190 # Using chrome/test/data/google/logo.gif as the test image
1191 test_image_path = ['google', 'logo.gif']
1192 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1193 if not os.path.isfile(gif_path):
1194 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001195 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001196 return True
1197
1198 f = open(gif_path, "rb")
1199 data = f.read()
1200 f.close()
1201
1202 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001203 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001204 self.send_header('Cache-control', 'max-age=60000')
1205 self.send_header('Etag', 'abc')
1206 self.end_headers()
1207 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001208 else:
1209 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001210 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001211 self.send_header('Cache-control', 'max-age=60000')
1212 self.send_header('Etag', 'abc')
1213 self.end_headers()
1214 self.wfile.write('<html><head>')
1215 self.wfile.write('<title>%s/%s</title>' % (username, password))
1216 self.wfile.write('</head><body>')
1217 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001218 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001219 self.wfile.write('</body></html>')
1220
rvargas@google.com54453b72011-05-19 01:11:11 +00001221 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001222 return True
1223
tonyg@chromium.org75054202010-03-31 22:06:10 +00001224 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001225 """Returns a nonce that's stable per request path for the server's lifetime.
1226 This is a fake implementation. A real implementation would only use a given
1227 nonce a single time (hence the name n-once). However, for the purposes of
1228 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001229
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001230 Args:
1231 force_reset: Iff set, the nonce will be changed. Useful for testing the
1232 "stale" response.
1233 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001234
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001235 if force_reset or not self.server.nonce_time:
1236 self.server.nonce_time = time.time()
1237 return hashlib.md5('privatekey%s%d' %
1238 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001239
1240 def AuthDigestHandler(self):
1241 """This handler tests 'Digest' authentication.
1242
1243 It just sends a page with title 'user/pass' if you succeed.
1244
1245 A stale response is sent iff "stale" is present in the request path.
1246 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001247
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001248 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001249 return False
1250
tonyg@chromium.org75054202010-03-31 22:06:10 +00001251 stale = 'stale' in self.path
1252 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001253 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001254 password = 'secret'
1255 realm = 'testrealm'
1256
1257 auth = self.headers.getheader('authorization')
1258 pairs = {}
1259 try:
1260 if not auth:
1261 raise Exception('no auth')
1262 if not auth.startswith('Digest'):
1263 raise Exception('not digest')
1264 # Pull out all the name="value" pairs as a dictionary.
1265 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1266
1267 # Make sure it's all valid.
1268 if pairs['nonce'] != nonce:
1269 raise Exception('wrong nonce')
1270 if pairs['opaque'] != opaque:
1271 raise Exception('wrong opaque')
1272
1273 # Check the 'response' value and make sure it matches our magic hash.
1274 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001275 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001276 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001277 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001278 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001279 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001280 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1281 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001282 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001283
1284 if pairs['response'] != response:
1285 raise Exception('wrong password')
1286 except Exception, e:
1287 # Authentication failed.
1288 self.send_response(401)
1289 hdr = ('Digest '
1290 'realm="%s", '
1291 'domain="/", '
1292 'qop="auth", '
1293 'algorithm=MD5, '
1294 'nonce="%s", '
1295 'opaque="%s"') % (realm, nonce, opaque)
1296 if stale:
1297 hdr += ', stale="TRUE"'
1298 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001299 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001300 self.end_headers()
1301 self.wfile.write('<html><head>')
1302 self.wfile.write('<title>Denied: %s</title>' % e)
1303 self.wfile.write('</head><body>')
1304 self.wfile.write('auth=%s<p>' % auth)
1305 self.wfile.write('pairs=%s<p>' % pairs)
1306 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1307 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1308 self.wfile.write('</body></html>')
1309 return True
1310
1311 # Authentication successful.
1312 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001313 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001314 self.end_headers()
1315 self.wfile.write('<html><head>')
1316 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1317 self.wfile.write('</head><body>')
1318 self.wfile.write('auth=%s<p>' % auth)
1319 self.wfile.write('pairs=%s<p>' % pairs)
1320 self.wfile.write('</body></html>')
1321
1322 return True
1323
1324 def SlowServerHandler(self):
1325 """Wait for the user suggested time before responding. The syntax is
1326 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001327
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001328 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001329 return False
1330 query_char = self.path.find('?')
1331 wait_sec = 1.0
1332 if query_char >= 0:
1333 try:
davidben05f82202015-03-31 13:48:07 -07001334 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001335 except ValueError:
1336 pass
1337 time.sleep(wait_sec)
1338 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001339 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001340 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001341 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001342 return True
1343
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001344 def ChunkedServerHandler(self):
1345 """Send chunked response. Allows to specify chunks parameters:
1346 - waitBeforeHeaders - ms to wait before sending headers
1347 - waitBetweenChunks - ms to wait between chunks
1348 - chunkSize - size of each chunk in bytes
1349 - chunksNumber - number of chunks
1350 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1351 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001352
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001353 if not self._ShouldHandleRequest("/chunked"):
1354 return False
1355 query_char = self.path.find('?')
1356 chunkedSettings = {'waitBeforeHeaders' : 0,
1357 'waitBetweenChunks' : 0,
1358 'chunkSize' : 5,
1359 'chunksNumber' : 5}
1360 if query_char >= 0:
1361 params = self.path[query_char + 1:].split('&')
1362 for param in params:
1363 keyValue = param.split('=')
1364 if len(keyValue) == 2:
1365 try:
1366 chunkedSettings[keyValue[0]] = int(keyValue[1])
1367 except ValueError:
1368 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001369 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001370 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1371 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001372 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001373 self.send_header('Connection', 'close')
1374 self.send_header('Transfer-Encoding', 'chunked')
1375 self.end_headers()
1376 # Chunked encoding: sending all chunks, then final zero-length chunk and
1377 # then final CRLF.
1378 for i in range(0, chunkedSettings['chunksNumber']):
1379 if i > 0:
1380 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1381 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001382 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001383 self.sendChunkHelp('')
1384 return True
1385
creis@google.com2f4f6a42011-03-25 19:44:19 +00001386 def NoContentHandler(self):
1387 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001388
creis@google.com2f4f6a42011-03-25 19:44:19 +00001389 if not self._ShouldHandleRequest("/nocontent"):
1390 return False
1391 self.send_response(204)
1392 self.end_headers()
1393 return True
1394
initial.commit94958cf2008-07-26 22:42:52 +00001395 def ServerRedirectHandler(self):
1396 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001397 '/server-redirect?http://foo.bar/asdf' to redirect to
1398 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001399
1400 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001401 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001402 return False
1403
1404 query_char = self.path.find('?')
1405 if query_char < 0 or len(self.path) <= query_char + 1:
1406 self.sendRedirectHelp(test_name)
1407 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001408 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001409
1410 self.send_response(301) # moved permanently
1411 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001412 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001413 self.end_headers()
1414 self.wfile.write('<html><head>')
1415 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1416
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001417 return True
initial.commit94958cf2008-07-26 22:42:52 +00001418
naskoe7a0d0d2014-09-29 08:53:05 -07001419 def CrossSiteRedirectHandler(self):
1420 """Sends a server redirect to the given site. The syntax is
1421 '/cross-site/hostname/...' to redirect to //hostname/...
1422 It is used to navigate between different Sites, causing
1423 cross-site/cross-process navigations in the browser."""
1424
1425 test_name = "/cross-site"
1426 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001427 return False
1428
1429 params = urllib.unquote(self.path[(len(test_name) + 1):])
1430 slash = params.find('/')
1431 if slash < 0:
1432 self.sendRedirectHelp(test_name)
1433 return True
1434
1435 host = params[:slash]
1436 path = params[(slash+1):]
1437 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1438
1439 self.send_response(301) # moved permanently
1440 self.send_header('Location', dest)
1441 self.send_header('Content-Type', 'text/html')
1442 self.end_headers()
1443 self.wfile.write('<html><head>')
1444 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1445
1446 return True
1447
initial.commit94958cf2008-07-26 22:42:52 +00001448 def ClientRedirectHandler(self):
1449 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001450 '/client-redirect?http://foo.bar/asdf' to redirect to
1451 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001452
1453 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001454 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001455 return False
1456
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001457 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001458 if query_char < 0 or len(self.path) <= query_char + 1:
1459 self.sendRedirectHelp(test_name)
1460 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001461 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001462
1463 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001464 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001465 self.end_headers()
1466 self.wfile.write('<html><head>')
1467 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1468 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1469
1470 return True
1471
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001472 def GetSSLSessionCacheHandler(self):
1473 """Send a reply containing a log of the session cache operations."""
1474
1475 if not self._ShouldHandleRequest('/ssl-session-cache'):
1476 return False
1477
1478 self.send_response(200)
1479 self.send_header('Content-Type', 'text/plain')
1480 self.end_headers()
1481 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001482 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001483 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001484 self.wfile.write('Pass --https-record-resume in order to use' +
1485 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001486 return True
1487
1488 for (action, sessionID) in log:
1489 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001490 return True
1491
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001492 def SSLManySmallRecords(self):
1493 """Sends a reply consisting of a variety of small writes. These will be
1494 translated into a series of small SSL records when used over an HTTPS
1495 server."""
1496
1497 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1498 return False
1499
1500 self.send_response(200)
1501 self.send_header('Content-Type', 'text/plain')
1502 self.end_headers()
1503
1504 # Write ~26K of data, in 1350 byte chunks
1505 for i in xrange(20):
1506 self.wfile.write('*' * 1350)
1507 self.wfile.flush()
1508 return True
1509
agl@chromium.org04700be2013-03-02 18:40:41 +00001510 def GetChannelID(self):
1511 """Send a reply containing the hashed ChannelID that the client provided."""
1512
1513 if not self._ShouldHandleRequest('/channel-id'):
1514 return False
1515
1516 self.send_response(200)
1517 self.send_header('Content-Type', 'text/plain')
1518 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001519 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001520 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1521 return True
1522
pneubeckfd4f0442015-08-07 04:55:10 -07001523 def GetClientCert(self):
1524 """Send a reply whether a client certificate was provided."""
1525
1526 if not self._ShouldHandleRequest('/client-cert'):
1527 return False
1528
1529 self.send_response(200)
1530 self.send_header('Content-Type', 'text/plain')
1531 self.end_headers()
1532
1533 cert_chain = self.server.tlsConnection.session.clientCertChain
1534 if cert_chain != None:
1535 self.wfile.write('got client cert with fingerprint: ' +
1536 cert_chain.getFingerprint())
1537 else:
1538 self.wfile.write('got no client cert')
1539 return True
1540
davidben599e7e72014-09-03 16:19:09 -07001541 def ClientCipherListHandler(self):
1542 """Send a reply containing the cipher suite list that the client
1543 provided. Each cipher suite value is serialized in decimal, followed by a
1544 newline."""
1545
1546 if not self._ShouldHandleRequest('/client-cipher-list'):
1547 return False
1548
1549 self.send_response(200)
1550 self.send_header('Content-Type', 'text/plain')
1551 self.end_headers()
1552
davidben11682512014-10-06 21:09:11 -07001553 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1554 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001555 return True
1556
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001557 def CloseSocketHandler(self):
1558 """Closes the socket without sending anything."""
1559
1560 if not self._ShouldHandleRequest('/close-socket'):
1561 return False
1562
1563 self.wfile.close()
1564 return True
1565
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001566 def RangeResetHandler(self):
1567 """Send data broken up by connection resets every N (default 4K) bytes.
1568 Support range requests. If the data requested doesn't straddle a reset
1569 boundary, it will all be sent. Used for testing resuming downloads."""
1570
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001571 def DataForRange(start, end):
1572 """Data to be provided for a particular range of bytes."""
1573 # Offset and scale to avoid too obvious (and hence potentially
1574 # collidable) data.
1575 return ''.join([chr(y % 256)
1576 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1577
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001578 if not self._ShouldHandleRequest('/rangereset'):
1579 return False
1580
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001581 # HTTP/1.1 is required for ETag and range support.
1582 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001583 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1584
1585 # Defaults
1586 size = 8000
1587 # Note that the rst is sent just before sending the rst_boundary byte.
1588 rst_boundary = 4000
1589 respond_to_range = True
1590 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001591 rst_limit = -1
1592 token = 'DEFAULT'
1593 fail_precondition = 0
1594 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001595
1596 # Parse the query
1597 qdict = urlparse.parse_qs(query, True)
1598 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001599 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001600 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001601 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001602 if 'token' in qdict:
1603 # Identifying token for stateful tests.
1604 token = qdict['token'][0]
1605 if 'rst_limit' in qdict:
1606 # Max number of rsts for a given token.
1607 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001608 if 'bounce_range' in qdict:
1609 respond_to_range = False
1610 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001611 # Note that hold_for_signal will not work with null range requests;
1612 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001613 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001614 if 'no_verifiers' in qdict:
1615 send_verifiers = False
1616 if 'fail_precondition' in qdict:
1617 fail_precondition = int(qdict['fail_precondition'][0])
1618
1619 # Record already set information, or set it.
1620 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1621 if rst_limit != 0:
1622 TestPageHandler.rst_limits[token] -= 1
1623 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1624 token, fail_precondition)
1625 if fail_precondition != 0:
1626 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001627
1628 first_byte = 0
1629 last_byte = size - 1
1630
1631 # Does that define what we want to return, or do we need to apply
1632 # a range?
1633 range_response = False
1634 range_header = self.headers.getheader('range')
1635 if range_header and respond_to_range:
1636 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1637 if mo.group(1):
1638 first_byte = int(mo.group(1))
1639 if mo.group(2):
1640 last_byte = int(mo.group(2))
1641 if last_byte > size - 1:
1642 last_byte = size - 1
1643 range_response = True
1644 if last_byte < first_byte:
1645 return False
1646
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001647 if (fail_precondition and
1648 (self.headers.getheader('If-Modified-Since') or
1649 self.headers.getheader('If-Match'))):
1650 self.send_response(412)
1651 self.end_headers()
1652 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001653
1654 if range_response:
1655 self.send_response(206)
1656 self.send_header('Content-Range',
1657 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1658 else:
1659 self.send_response(200)
1660 self.send_header('Content-Type', 'application/octet-stream')
1661 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001662 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001663 # If fail_precondition is non-zero, then the ETag for each request will be
1664 # different.
1665 etag = "%s%d" % (token, fail_precondition)
1666 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001667 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001668 self.end_headers()
1669
1670 if hold_for_signal:
1671 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1672 # a single byte, the self.server.handle_request() below hangs
1673 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001674 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001675 first_byte = first_byte + 1
1676 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001677 self.server.wait_for_download = True
1678 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001679 self.server.handle_request()
1680
1681 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001682 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001683 # No RST has been requested in this range, so we don't need to
1684 # do anything fancy; just write the data and let the python
1685 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001686 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001687 self.wfile.flush()
1688 return True
1689
1690 # We're resetting the connection part way in; go to the RST
1691 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001692 # Because socket semantics do not guarantee that all the data will be
1693 # sent when using the linger semantics to hard close a socket,
1694 # we send the data and then wait for our peer to release us
1695 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001696 data = DataForRange(first_byte, possible_rst)
1697 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001698 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001699 self.server.wait_for_download = True
1700 while self.server.wait_for_download:
1701 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001702 l_onoff = 1 # Linger is active.
1703 l_linger = 0 # Seconds to linger for.
1704 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1705 struct.pack('ii', l_onoff, l_linger))
1706
1707 # Close all duplicates of the underlying socket to force the RST.
1708 self.wfile.close()
1709 self.rfile.close()
1710 self.connection.close()
1711
1712 return True
1713
initial.commit94958cf2008-07-26 22:42:52 +00001714 def DefaultResponseHandler(self):
1715 """This is the catch-all response handler for requests that aren't handled
1716 by one of the special handlers above.
1717 Note that we specify the content-length as without it the https connection
1718 is not closed properly (and the browser keeps expecting data)."""
1719
1720 contents = "Default response given for path: " + self.path
1721 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001722 self.send_header('Content-Type', 'text/html')
1723 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001724 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001725 if (self.command != 'HEAD'):
1726 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001727 return True
1728
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001729 def RedirectConnectHandler(self):
1730 """Sends a redirect to the CONNECT request for www.redirect.com. This
1731 response is not specified by the RFC, so the browser should not follow
1732 the redirect."""
1733
1734 if (self.path.find("www.redirect.com") < 0):
1735 return False
1736
1737 dest = "http://www.destination.com/foo.js"
1738
1739 self.send_response(302) # moved temporarily
1740 self.send_header('Location', dest)
1741 self.send_header('Connection', 'close')
1742 self.end_headers()
1743 return True
1744
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001745 def ServerAuthConnectHandler(self):
1746 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1747 response doesn't make sense because the proxy server cannot request
1748 server authentication."""
1749
1750 if (self.path.find("www.server-auth.com") < 0):
1751 return False
1752
1753 challenge = 'Basic realm="WallyWorld"'
1754
1755 self.send_response(401) # unauthorized
1756 self.send_header('WWW-Authenticate', challenge)
1757 self.send_header('Connection', 'close')
1758 self.end_headers()
1759 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001760
1761 def DefaultConnectResponseHandler(self):
1762 """This is the catch-all response handler for CONNECT requests that aren't
1763 handled by one of the special handlers above. Real Web servers respond
1764 with 400 to CONNECT requests."""
1765
1766 contents = "Your client has issued a malformed or illegal request."
1767 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001768 self.send_header('Content-Type', 'text/html')
1769 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001770 self.end_headers()
1771 self.wfile.write(contents)
1772 return True
1773
initial.commit94958cf2008-07-26 22:42:52 +00001774 # called by the redirect handling function when there is no parameter
1775 def sendRedirectHelp(self, redirect_name):
1776 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001777 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001778 self.end_headers()
1779 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1780 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1781 self.wfile.write('</body></html>')
1782
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001783 # called by chunked handling function
1784 def sendChunkHelp(self, chunk):
1785 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1786 self.wfile.write('%X\r\n' % len(chunk))
1787 self.wfile.write(chunk)
1788 self.wfile.write('\r\n')
1789
akalin@chromium.org154bb132010-11-12 02:20:27 +00001790
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001791class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001792 def __init__(self, request, client_address, socket_server):
1793 handlers = [self.OCSPResponse]
1794 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001795 testserver_base.BasePageHandler.__init__(self, request, client_address,
1796 socket_server, [], handlers, [],
1797 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001798
1799 def OCSPResponse(self):
1800 self.send_response(200)
1801 self.send_header('Content-Type', 'application/ocsp-response')
1802 self.send_header('Content-Length', str(len(self.ocsp_response)))
1803 self.end_headers()
1804
1805 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001806
mattm@chromium.org830a3712012-11-07 23:00:07 +00001807
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001808class TCPEchoHandler(SocketServer.BaseRequestHandler):
1809 """The RequestHandler class for TCP echo server.
1810
1811 It is instantiated once per connection to the server, and overrides the
1812 handle() method to implement communication to the client.
1813 """
1814
1815 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001816 """Handles the request from the client and constructs a response."""
1817
1818 data = self.request.recv(65536).strip()
1819 # Verify the "echo request" message received from the client. Send back
1820 # "echo response" message if "echo request" message is valid.
1821 try:
1822 return_data = echo_message.GetEchoResponseData(data)
1823 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001824 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001825 except ValueError:
1826 return
1827
1828 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001829
1830
1831class UDPEchoHandler(SocketServer.BaseRequestHandler):
1832 """The RequestHandler class for UDP echo server.
1833
1834 It is instantiated once per connection to the server, and overrides the
1835 handle() method to implement communication to the client.
1836 """
1837
1838 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001839 """Handles the request from the client and constructs a response."""
1840
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001841 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001842 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001843 # Verify the "echo request" message received from the client. Send back
1844 # "echo response" message if "echo request" message is valid.
1845 try:
1846 return_data = echo_message.GetEchoResponseData(data)
1847 if not return_data:
1848 return
1849 except ValueError:
1850 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001851 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001852
1853
bashi@chromium.org33233532012-09-08 17:37:24 +00001854class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1855 """A request handler that behaves as a proxy server which requires
1856 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1857 """
1858
1859 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1860
1861 def parse_request(self):
1862 """Overrides parse_request to check credential."""
1863
1864 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1865 return False
1866
1867 auth = self.headers.getheader('Proxy-Authorization')
1868 if auth != self._AUTH_CREDENTIAL:
1869 self.send_response(407)
1870 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1871 self.end_headers()
1872 return False
1873
1874 return True
1875
1876 def _start_read_write(self, sock):
1877 sock.setblocking(0)
1878 self.request.setblocking(0)
1879 rlist = [self.request, sock]
1880 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001881 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001882 if errors:
1883 self.send_response(500)
1884 self.end_headers()
1885 return
1886 for s in ready_sockets:
1887 received = s.recv(1024)
1888 if len(received) == 0:
1889 return
1890 if s == self.request:
1891 other = sock
1892 else:
1893 other = self.request
1894 other.send(received)
1895
1896 def _do_common_method(self):
1897 url = urlparse.urlparse(self.path)
1898 port = url.port
1899 if not port:
1900 if url.scheme == 'http':
1901 port = 80
1902 elif url.scheme == 'https':
1903 port = 443
1904 if not url.hostname or not port:
1905 self.send_response(400)
1906 self.end_headers()
1907 return
1908
1909 if len(url.path) == 0:
1910 path = '/'
1911 else:
1912 path = url.path
1913 if len(url.query) > 0:
1914 path = '%s?%s' % (url.path, url.query)
1915
1916 sock = None
1917 try:
1918 sock = socket.create_connection((url.hostname, port))
1919 sock.send('%s %s %s\r\n' % (
1920 self.command, path, self.protocol_version))
1921 for header in self.headers.headers:
1922 header = header.strip()
1923 if (header.lower().startswith('connection') or
1924 header.lower().startswith('proxy')):
1925 continue
1926 sock.send('%s\r\n' % header)
1927 sock.send('\r\n')
1928 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001929 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001930 self.send_response(500)
1931 self.end_headers()
1932 finally:
1933 if sock is not None:
1934 sock.close()
1935
1936 def do_CONNECT(self):
1937 try:
1938 pos = self.path.rfind(':')
1939 host = self.path[:pos]
1940 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001941 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001942 self.send_response(400)
1943 self.end_headers()
1944
1945 try:
1946 sock = socket.create_connection((host, port))
1947 self.send_response(200, 'Connection established')
1948 self.end_headers()
1949 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001950 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001951 self.send_response(500)
1952 self.end_headers()
1953 finally:
1954 sock.close()
1955
1956 def do_GET(self):
1957 self._do_common_method()
1958
1959 def do_HEAD(self):
1960 self._do_common_method()
1961
1962
mattm@chromium.org830a3712012-11-07 23:00:07 +00001963class ServerRunner(testserver_base.TestServerRunner):
1964 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001965
mattm@chromium.org830a3712012-11-07 23:00:07 +00001966 def __init__(self):
1967 super(ServerRunner, self).__init__()
1968 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001969
mattm@chromium.org830a3712012-11-07 23:00:07 +00001970 def __make_data_dir(self):
1971 if self.options.data_dir:
1972 if not os.path.isdir(self.options.data_dir):
1973 raise testserver_base.OptionError('specified data dir not found: ' +
1974 self.options.data_dir + ' exiting...')
1975 my_data_dir = self.options.data_dir
1976 else:
1977 # Create the default path to our data dir, relative to the exe dir.
1978 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1979 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001980
mattm@chromium.org830a3712012-11-07 23:00:07 +00001981 #TODO(ibrar): Must use Find* funtion defined in google\tools
1982 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001983
mattm@chromium.org830a3712012-11-07 23:00:07 +00001984 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001985
mattm@chromium.org830a3712012-11-07 23:00:07 +00001986 def create_server(self, server_data):
1987 port = self.options.port
1988 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001989
estark21667d62015-04-08 21:00:16 -07001990 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1991 # will result in a call to |getaddrinfo|, which fails with "nodename
1992 # nor servname provided" for localhost:0 on 10.6.
1993 if self.options.server_type == SERVER_WEBSOCKET and \
1994 host == "localhost" and \
1995 port == 0:
1996 host = "127.0.0.1"
1997
mattm@chromium.org830a3712012-11-07 23:00:07 +00001998 if self.options.server_type == SERVER_HTTP:
1999 if self.options.https:
2000 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08002001 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002 if self.options.cert_and_key_file:
2003 if not os.path.isfile(self.options.cert_and_key_file):
2004 raise testserver_base.OptionError(
2005 'specified server cert file not found: ' +
2006 self.options.cert_and_key_file + ' exiting...')
2007 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002008 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 # generate a new certificate and run an OCSP server for it.
2010 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002011 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00002012 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002013
mattm@chromium.org830a3712012-11-07 23:00:07 +00002014 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002015
mattm@chromium.org830a3712012-11-07 23:00:07 +00002016 if self.options.ocsp == 'ok':
2017 ocsp_state = minica.OCSP_STATE_GOOD
2018 elif self.options.ocsp == 'revoked':
2019 ocsp_state = minica.OCSP_STATE_REVOKED
2020 elif self.options.ocsp == 'invalid':
2021 ocsp_state = minica.OCSP_STATE_INVALID
2022 elif self.options.ocsp == 'unauthorized':
2023 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2024 elif self.options.ocsp == 'unknown':
2025 ocsp_state = minica.OCSP_STATE_UNKNOWN
2026 else:
2027 raise testserver_base.OptionError('unknown OCSP status: ' +
2028 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002029
mattm@chromium.org830a3712012-11-07 23:00:07 +00002030 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2031 subject = "127.0.0.1",
2032 ocsp_url = ("http://%s:%d/ocsp" %
2033 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002034 ocsp_state = ocsp_state,
2035 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002036
davidben3e2564a2014-11-07 18:51:00 -08002037 if self.options.ocsp_server_unavailable:
2038 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2039 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2040 else:
2041 self.__ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org830a3712012-11-07 23:00:07 +00002042
2043 for ca_cert in self.options.ssl_client_ca:
2044 if not os.path.isfile(ca_cert):
2045 raise testserver_base.OptionError(
2046 'specified trusted client CA file not found: ' + ca_cert +
2047 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002048
2049 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002050 if self.options.staple_ocsp_response:
2051 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002052
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2054 self.options.ssl_client_auth,
2055 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002056 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002057 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002058 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002059 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002060 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002061 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002062 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002063 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002064 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002065 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002066 stapled_ocsp_response,
nharper1e8bf4b2015-09-18 12:23:02 -07002067 self.options.alert_after_handshake,
2068 self.options.disable_channel_id,
2069 self.options.disable_extended_master_secret,
2070 self.options.token_binding_params)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002071 print 'HTTPS server started on https://%s:%d...' % \
2072 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002073 else:
2074 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002075 print 'HTTP server started on http://%s:%d...' % \
2076 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002077
2078 server.data_dir = self.__make_data_dir()
2079 server.file_root_url = self.options.file_root_url
2080 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002081 elif self.options.server_type == SERVER_WEBSOCKET:
2082 # Launch pywebsocket via WebSocketServer.
2083 logger = logging.getLogger()
2084 logger.addHandler(logging.StreamHandler())
2085 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2086 # is required to work correctly. It should be fixed from pywebsocket side.
2087 os.chdir(self.__make_data_dir())
2088 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002089 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002090 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002091 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002092 websocket_options.use_tls = True
2093 websocket_options.private_key = self.options.cert_and_key_file
2094 websocket_options.certificate = self.options.cert_and_key_file
2095 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002096 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002097 websocket_options.tls_client_auth = True
2098 if len(self.options.ssl_client_ca) != 1:
2099 raise testserver_base.OptionError(
2100 'one trusted client CA file should be specified')
2101 if not os.path.isfile(self.options.ssl_client_ca[0]):
2102 raise testserver_base.OptionError(
2103 'specified trusted client CA file not found: ' +
2104 self.options.ssl_client_ca[0] + ' exiting...')
2105 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002106 print 'Trying to start websocket server on %s://%s:%d...' % \
2107 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002108 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002109 print 'WebSocket server started on %s://%s:%d...' % \
2110 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002111 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002112 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002113 elif self.options.server_type == SERVER_TCP_ECHO:
2114 # Used for generating the key (randomly) that encodes the "echo request"
2115 # message.
2116 random.seed()
2117 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002118 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002119 server_data['port'] = server.server_port
2120 elif self.options.server_type == SERVER_UDP_ECHO:
2121 # Used for generating the key (randomly) that encodes the "echo request"
2122 # message.
2123 random.seed()
2124 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002125 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002126 server_data['port'] = server.server_port
2127 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2128 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002129 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130 server_data['port'] = server.server_port
2131 elif self.options.server_type == SERVER_FTP:
2132 my_data_dir = self.__make_data_dir()
2133
2134 # Instantiate a dummy authorizer for managing 'virtual' users
2135 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2136
xleng9d4c45f2015-05-04 16:26:12 -07002137 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002138 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2139
xleng9d4c45f2015-05-04 16:26:12 -07002140 # Define a read-only anonymous user unless disabled
2141 if not self.options.no_anonymous_ftp_user:
2142 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002143
2144 # Instantiate FTP handler class
2145 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2146 ftp_handler.authorizer = authorizer
2147
2148 # Define a customized banner (string returned when client connects)
2149 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2150 pyftpdlib.ftpserver.__ver__)
2151
2152 # Instantiate FTP server class and listen to address:port
2153 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2154 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002155 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002156 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002157 raise testserver_base.OptionError('unknown server type' +
2158 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002159
mattm@chromium.org830a3712012-11-07 23:00:07 +00002160 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002161
mattm@chromium.org830a3712012-11-07 23:00:07 +00002162 def run_server(self):
2163 if self.__ocsp_server:
2164 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002165
mattm@chromium.org830a3712012-11-07 23:00:07 +00002166 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002167
mattm@chromium.org830a3712012-11-07 23:00:07 +00002168 if self.__ocsp_server:
2169 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002170
mattm@chromium.org830a3712012-11-07 23:00:07 +00002171 def add_options(self):
2172 testserver_base.TestServerRunner.add_options(self)
2173 self.option_parser.add_option('-f', '--ftp', action='store_const',
2174 const=SERVER_FTP, default=SERVER_HTTP,
2175 dest='server_type',
2176 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002177 self.option_parser.add_option('--tcp-echo', action='store_const',
2178 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2179 dest='server_type',
2180 help='start up a tcp echo server.')
2181 self.option_parser.add_option('--udp-echo', action='store_const',
2182 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2183 dest='server_type',
2184 help='start up a udp echo server.')
2185 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2186 const=SERVER_BASIC_AUTH_PROXY,
2187 default=SERVER_HTTP, dest='server_type',
2188 help='start up a proxy server which requires '
2189 'basic authentication.')
2190 self.option_parser.add_option('--websocket', action='store_const',
2191 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2192 dest='server_type',
2193 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002194 self.option_parser.add_option('--https', action='store_true',
2195 dest='https', help='Specify that https '
2196 'should be used.')
2197 self.option_parser.add_option('--cert-and-key-file',
2198 dest='cert_and_key_file', help='specify the '
2199 'path to the file containing the certificate '
2200 'and private key for the server in PEM '
2201 'format')
2202 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2203 help='The type of OCSP response generated '
2204 'for the automatically generated '
2205 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002206 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2207 default=0, type=int,
2208 help='If non-zero then the generated '
2209 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002210 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2211 default='0', type='int',
2212 help='If nonzero, certain TLS connections '
2213 'will be aborted in order to test version '
2214 'fallback. 1 means all TLS versions will be '
2215 'aborted. 2 means TLS 1.1 or higher will be '
2216 'aborted. 3 means TLS 1.2 or higher will be '
2217 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002218 self.option_parser.add_option('--tls-intolerance-type',
2219 dest='tls_intolerance_type',
2220 default="alert",
2221 help='Controls how the server reacts to a '
2222 'TLS version it is intolerant to. Valid '
2223 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002224 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2225 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002226 default='',
2227 help='Base64 encoded SCT list. If set, '
2228 'server will respond with a '
2229 'signed_certificate_timestamp TLS extension '
2230 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002231 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2232 default=False, const=True,
2233 action='store_const',
2234 help='If given, TLS_FALLBACK_SCSV support '
2235 'will be enabled. This causes the server to '
2236 'reject fallback connections from compatible '
2237 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002238 self.option_parser.add_option('--staple-ocsp-response',
2239 dest='staple_ocsp_response',
2240 default=False, action='store_true',
2241 help='If set, server will staple the OCSP '
2242 'response whenever OCSP is on and the client '
2243 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002244 self.option_parser.add_option('--https-record-resume',
2245 dest='record_resume', const=True,
2246 default=False, action='store_const',
2247 help='Record resumption cache events rather '
2248 'than resuming as normal. Allows the use of '
2249 'the /ssl-session-cache request')
2250 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2251 help='Require SSL client auth on every '
2252 'connection.')
2253 self.option_parser.add_option('--ssl-client-ca', action='append',
2254 default=[], help='Specify that the client '
2255 'certificate request should include the CA '
2256 'named in the subject of the DER-encoded '
2257 'certificate contained in the specified '
2258 'file. This option may appear multiple '
2259 'times, indicating multiple CA names should '
2260 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002261 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2262 default=[], help='Specify that the client '
2263 'certificate request should include the '
2264 'specified certificate_type value. This '
2265 'option may appear multiple times, '
2266 'indicating multiple values should be send '
2267 'in the request. Valid values are '
2268 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2269 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002270 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2271 help='Specify the bulk encryption '
2272 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002273 'SSL server. Valid values are "aes128gcm", '
2274 '"aes256", "aes128", "3des", "rc4". If '
2275 'omitted, all algorithms will be used. This '
2276 'option may appear multiple times, '
2277 'indicating multiple algorithms should be '
2278 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002279 self.option_parser.add_option('--ssl-key-exchange', action='append',
2280 help='Specify the key exchange algorithm(s)'
2281 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002282 'Valid values are "rsa", "dhe_rsa", '
2283 '"ecdhe_rsa". If omitted, all algorithms '
2284 'will be used. This option may appear '
2285 'multiple times, indicating multiple '
2286 'algorithms should be enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002287 # TODO(davidben): Add ALPN support to tlslite.
2288 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2289 default=False, const=True,
2290 action='store_const',
2291 help='Enable server support for the NPN '
2292 'extension. The server will advertise '
2293 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002294 self.option_parser.add_option('--file-root-url', default='/files/',
2295 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002296 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2297 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2298 dest='ws_basic_auth',
2299 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002300 self.option_parser.add_option('--ocsp-server-unavailable',
2301 dest='ocsp_server_unavailable',
2302 default=False, action='store_true',
2303 help='If set, the OCSP server will return '
2304 'a tryLater status rather than the actual '
2305 'OCSP response.')
davidben21cda342015-03-17 18:04:28 -07002306 self.option_parser.add_option('--alert-after-handshake',
2307 dest='alert_after_handshake',
2308 default=False, action='store_true',
2309 help='If set, the server will send a fatal '
2310 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002311 self.option_parser.add_option('--no-anonymous-ftp-user',
2312 dest='no_anonymous_ftp_user',
2313 default=False, action='store_true',
2314 help='If set, the FTP server will not create '
2315 'an anonymous user.')
nharper1e8bf4b2015-09-18 12:23:02 -07002316 self.option_parser.add_option('--disable-channel-id', action='store_true')
2317 self.option_parser.add_option('--disable-extended-master-secret',
2318 action='store_true')
2319 self.option_parser.add_option('--token-binding-params', action='append',
2320 default=[], type='int')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002321
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002322
initial.commit94958cf2008-07-26 22:42:52 +00002323if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002324 sys.exit(ServerRunner().main())