blob: b2c7d55fdaf604bbca3b825d40ec9a0f01556171 [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,
rsleevi8146efa2015-03-16 12:31:24 -0700160 fallback_scsv_enabled, ocsp_response):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000161 self.cert_chain = tlslite.api.X509CertChain()
162 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000163 # Force using only python implementation - otherwise behavior is different
164 # depending on whether m2crypto Python module is present (error is thrown
165 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
166 # the hood.
167 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
168 private=True,
169 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000170 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000171 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000172 self.ssl_client_cert_types = []
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000173 if enable_npn:
174 self.next_protos = ['http/1.1']
175 else:
176 self.next_protos = None
ekasper@google.com24aa8222013-11-28 13:43:26 +0000177 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000178 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000179 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000180
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000181 if ssl_client_auth:
182 for ca_file in ssl_client_cas:
183 s = open(ca_file).read()
184 x509 = tlslite.api.X509()
185 x509.parse(s)
186 self.ssl_client_cas.append(x509.subject)
187
188 for cert_type in ssl_client_cert_types:
189 self.ssl_client_cert_types.append({
190 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
191 "dss_sign": tlslite.api.ClientCertificateType.dss_sign,
192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800196 # Enable SSLv3 for testing purposes.
197 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000202 if tls_intolerant != 0:
203 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
204 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
initial.commit94958cf2008-07-26 22:42:52 +0000205
rsleevi8146efa2015-03-16 12:31:24 -0700206 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000207 # If record_resume_info is true then we'll replace the session cache with
208 # an object that records the lookups and inserts that it sees.
209 self.session_cache = RecordingSSLSessionCache()
210 else:
211 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000212 testserver_base.StoppableHTTPServer.__init__(self,
213 server_address,
214 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000215
216 def handshake(self, tlsConnection):
217 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000218
initial.commit94958cf2008-07-26 22:42:52 +0000219 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000220 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000221 tlsConnection.handshakeServer(certChain=self.cert_chain,
222 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000223 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000224 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000225 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000226 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000227 reqCertTypes=self.ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000228 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000229 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000230 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000231 fallbackSCSV=self.fallback_scsv_enabled,
232 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000233 tlsConnection.ignoreAbruptClose = True
234 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000235 except tlslite.api.TLSAbruptCloseError:
236 # Ignore abrupt close.
237 return True
initial.commit94958cf2008-07-26 22:42:52 +0000238 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000239 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000240 return False
241
akalin@chromium.org154bb132010-11-12 02:20:27 +0000242
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000243class FTPServer(testserver_base.ClientRestrictingServerMixIn,
244 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000245 """This is a specialization of FTPServer that adds client verification."""
246
247 pass
248
249
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000250class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
251 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000252 """A TCP echo server that echoes back what it has received."""
253
254 def server_bind(self):
255 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000256
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000257 SocketServer.TCPServer.server_bind(self)
258 host, port = self.socket.getsockname()[:2]
259 self.server_name = socket.getfqdn(host)
260 self.server_port = port
261
262 def serve_forever(self):
263 self.stop = False
264 self.nonce_time = None
265 while not self.stop:
266 self.handle_request()
267 self.socket.close()
268
269
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000270class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
271 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000272 """A UDP echo server that echoes back what it has received."""
273
274 def server_bind(self):
275 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000276
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000277 SocketServer.UDPServer.server_bind(self)
278 host, port = self.socket.getsockname()[:2]
279 self.server_name = socket.getfqdn(host)
280 self.server_port = port
281
282 def serve_forever(self):
283 self.stop = False
284 self.nonce_time = None
285 while not self.stop:
286 self.handle_request()
287 self.socket.close()
288
289
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000290class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000291 # Class variables to allow for persistence state between page handler
292 # invocations
293 rst_limits = {}
294 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000295
296 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000297 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000298 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000299 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000300 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000301 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000302 self.NoCacheMaxAgeTimeHandler,
303 self.NoCacheTimeHandler,
304 self.CacheTimeHandler,
305 self.CacheExpiresHandler,
306 self.CacheProxyRevalidateHandler,
307 self.CachePrivateHandler,
308 self.CachePublicHandler,
309 self.CacheSMaxAgeHandler,
310 self.CacheMustRevalidateHandler,
311 self.CacheMustRevalidateMaxAgeHandler,
312 self.CacheNoStoreHandler,
313 self.CacheNoStoreMaxAgeHandler,
314 self.CacheNoTransformHandler,
315 self.DownloadHandler,
316 self.DownloadFinishHandler,
317 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000318 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000319 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000320 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000321 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000322 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000323 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000324 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000325 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000326 self.AuthBasicHandler,
327 self.AuthDigestHandler,
328 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000329 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000330 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000331 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000332 self.ServerRedirectHandler,
naskoe7a0d0d2014-09-29 08:53:05 -0700333 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000334 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000335 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000336 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000337 self.GetChannelID,
davidben599e7e72014-09-03 16:19:09 -0700338 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000339 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000340 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000341 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000342 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000343 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000344 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000345 self.PostOnlyFileHandler,
346 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000347 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000348 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000349 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000350 head_handlers = [
351 self.FileHandler,
352 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000353
maruel@google.come250a9b2009-03-10 17:39:46 +0000354 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000355 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000356 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000357 'gif': 'image/gif',
358 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000359 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700360 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000361 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000362 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000363 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000364 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000365 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 }
initial.commit94958cf2008-07-26 22:42:52 +0000367 self._default_mime_type = 'text/html'
368
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000369 testserver_base.BasePageHandler.__init__(self, request, client_address,
370 socket_server, connect_handlers,
371 get_handlers, head_handlers,
372 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000373
initial.commit94958cf2008-07-26 22:42:52 +0000374 def GetMIMETypeFromName(self, file_name):
375 """Returns the mime type for the specified file_name. So far it only looks
376 at the file extension."""
377
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000378 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000379 if len(extension) == 0:
380 # no extension.
381 return self._default_mime_type
382
ericroman@google.comc17ca532009-05-07 03:51:05 +0000383 # extension starts with a dot, so we need to remove it
384 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000385
initial.commit94958cf2008-07-26 22:42:52 +0000386 def NoCacheMaxAgeTimeHandler(self):
387 """This request handler yields a page with the title set to the current
388 system time, and no caching requested."""
389
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000390 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000391 return False
392
393 self.send_response(200)
394 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000395 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000396 self.end_headers()
397
maruel@google.come250a9b2009-03-10 17:39:46 +0000398 self.wfile.write('<html><head><title>%s</title></head></html>' %
399 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000400
401 return True
402
403 def NoCacheTimeHandler(self):
404 """This request handler yields a page with the title set to the current
405 system time, and no caching requested."""
406
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000407 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000408 return False
409
410 self.send_response(200)
411 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000412 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.end_headers()
414
maruel@google.come250a9b2009-03-10 17:39:46 +0000415 self.wfile.write('<html><head><title>%s</title></head></html>' %
416 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000417
418 return True
419
420 def CacheTimeHandler(self):
421 """This request handler yields a page with the title set to the current
422 system time, and allows caching for one minute."""
423
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000424 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000425 return False
426
427 self.send_response(200)
428 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000429 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000430 self.end_headers()
431
maruel@google.come250a9b2009-03-10 17:39:46 +0000432 self.wfile.write('<html><head><title>%s</title></head></html>' %
433 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000434
435 return True
436
437 def CacheExpiresHandler(self):
438 """This request handler yields a page with the title set to the current
439 system time, and set the page to expire on 1 Jan 2099."""
440
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000441 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000442 return False
443
444 self.send_response(200)
445 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000446 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000447 self.end_headers()
448
maruel@google.come250a9b2009-03-10 17:39:46 +0000449 self.wfile.write('<html><head><title>%s</title></head></html>' %
450 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000451
452 return True
453
454 def CacheProxyRevalidateHandler(self):
455 """This request handler yields a page with the title set to the current
456 system time, and allows caching for 60 seconds"""
457
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000458 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000459 return False
460
461 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000462 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000463 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
464 self.end_headers()
465
maruel@google.come250a9b2009-03-10 17:39:46 +0000466 self.wfile.write('<html><head><title>%s</title></head></html>' %
467 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000468
469 return True
470
471 def CachePrivateHandler(self):
472 """This request handler yields a page with the title set to the current
473 system time, and allows caching for 5 seconds."""
474
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000475 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000476 return False
477
478 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000479 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000480 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.end_headers()
482
maruel@google.come250a9b2009-03-10 17:39:46 +0000483 self.wfile.write('<html><head><title>%s</title></head></html>' %
484 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000485
486 return True
487
488 def CachePublicHandler(self):
489 """This request handler yields a page with the title set to the current
490 system time, and allows caching for 5 seconds."""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000496 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000497 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000498 self.end_headers()
499
maruel@google.come250a9b2009-03-10 17:39:46 +0000500 self.wfile.write('<html><head><title>%s</title></head></html>' %
501 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000502
503 return True
504
505 def CacheSMaxAgeHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and does not allow for caching."""
508
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000509 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000510 return False
511
512 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000513 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000514 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
515 self.end_headers()
516
maruel@google.come250a9b2009-03-10 17:39:46 +0000517 self.wfile.write('<html><head><title>%s</title></head></html>' %
518 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000519
520 return True
521
522 def CacheMustRevalidateHandler(self):
523 """This request handler yields a page with the title set to the current
524 system time, and does not allow caching."""
525
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000526 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000527 return False
528
529 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000530 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000531 self.send_header('Cache-Control', 'must-revalidate')
532 self.end_headers()
533
maruel@google.come250a9b2009-03-10 17:39:46 +0000534 self.wfile.write('<html><head><title>%s</title></head></html>' %
535 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000536
537 return True
538
539 def CacheMustRevalidateMaxAgeHandler(self):
540 """This request handler yields a page with the title set to the current
541 system time, and does not allow caching event though max-age of 60
542 seconds is specified."""
543
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000544 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000545 return False
546
547 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000548 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000549 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
550 self.end_headers()
551
maruel@google.come250a9b2009-03-10 17:39:46 +0000552 self.wfile.write('<html><head><title>%s</title></head></html>' %
553 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000554
555 return True
556
initial.commit94958cf2008-07-26 22:42:52 +0000557 def CacheNoStoreHandler(self):
558 """This request handler yields a page with the title set to the current
559 system time, and does not allow the page to be stored."""
560
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000561 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000562 return False
563
564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000565 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000566 self.send_header('Cache-Control', 'no-store')
567 self.end_headers()
568
maruel@google.come250a9b2009-03-10 17:39:46 +0000569 self.wfile.write('<html><head><title>%s</title></head></html>' %
570 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000571
572 return True
573
574 def CacheNoStoreMaxAgeHandler(self):
575 """This request handler yields a page with the title set to the current
576 system time, and does not allow the page to be stored even though max-age
577 of 60 seconds is specified."""
578
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000579 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000580 return False
581
582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000583 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000584 self.send_header('Cache-Control', 'max-age=60, no-store')
585 self.end_headers()
586
maruel@google.come250a9b2009-03-10 17:39:46 +0000587 self.wfile.write('<html><head><title>%s</title></head></html>' %
588 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000589
590 return True
591
592
593 def CacheNoTransformHandler(self):
594 """This request handler yields a page with the title set to the current
595 system time, and does not allow the content to transformed during
596 user-agent caching"""
597
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000598 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000599 return False
600
601 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000602 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000603 self.send_header('Cache-Control', 'no-transform')
604 self.end_headers()
605
maruel@google.come250a9b2009-03-10 17:39:46 +0000606 self.wfile.write('<html><head><title>%s</title></head></html>' %
607 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000608
609 return True
610
611 def EchoHeader(self):
612 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000613
ananta@chromium.org219b2062009-10-23 16:09:41 +0000614 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000615
ananta@chromium.org56812d02011-04-07 17:52:05 +0000616 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000617 """This function echoes back the value of a specific request header while
618 allowing caching for 16 hours."""
619
ananta@chromium.org56812d02011-04-07 17:52:05 +0000620 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000621
622 def EchoHeaderHelper(self, echo_header):
623 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000624
ananta@chromium.org219b2062009-10-23 16:09:41 +0000625 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000626 return False
627
628 query_char = self.path.find('?')
629 if query_char != -1:
630 header_name = self.path[query_char+1:]
631
632 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000633 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000634 if echo_header == '/echoheadercache':
635 self.send_header('Cache-control', 'max-age=60000')
636 else:
637 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000638 # insert a vary header to properly indicate that the cachability of this
639 # request is subject to value of the request header being echoed.
640 if len(header_name) > 0:
641 self.send_header('Vary', header_name)
642 self.end_headers()
643
644 if len(header_name) > 0:
645 self.wfile.write(self.headers.getheader(header_name))
646
647 return True
648
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000649 def ReadRequestBody(self):
650 """This function reads the body of the current HTTP request, handling
651 both plain and chunked transfer encoded requests."""
652
653 if self.headers.getheader('transfer-encoding') != 'chunked':
654 length = int(self.headers.getheader('content-length'))
655 return self.rfile.read(length)
656
657 # Read the request body as chunks.
658 body = ""
659 while True:
660 line = self.rfile.readline()
661 length = int(line, 16)
662 if length == 0:
663 self.rfile.readline()
664 break
665 body += self.rfile.read(length)
666 self.rfile.read(2)
667 return body
668
initial.commit94958cf2008-07-26 22:42:52 +0000669 def EchoHandler(self):
670 """This handler just echoes back the payload of the request, for testing
671 form submission."""
672
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000673 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000674 return False
675
hirono2838c572015-01-21 12:18:11 -0800676 _, _, _, _, query, _ = urlparse.urlparse(self.path)
677 query_params = cgi.parse_qs(query, True)
678 if 'status' in query_params:
679 self.send_response(int(query_params['status'][0]))
680 else:
681 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000682 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000683 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000684 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000685 return True
686
687 def EchoTitleHandler(self):
688 """This handler is like Echo, but sets the page title to the request."""
689
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000690 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000691 return False
692
693 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000694 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000695 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000696 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000697 self.wfile.write('<html><head><title>')
698 self.wfile.write(request)
699 self.wfile.write('</title></head></html>')
700 return True
701
702 def EchoAllHandler(self):
703 """This handler yields a (more) human-readable page listing information
704 about the request header & contents."""
705
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000706 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000707 return False
708
709 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000710 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000711 self.end_headers()
712 self.wfile.write('<html><head><style>'
713 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
714 '</style></head><body>'
715 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000716 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000717 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000718
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000719 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000720 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000721 params = cgi.parse_qs(qs, keep_blank_values=1)
722
723 for param in params:
724 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000725
726 self.wfile.write('</pre>')
727
728 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
729
730 self.wfile.write('</body></html>')
731 return True
732
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000733 def EchoMultipartPostHandler(self):
734 """This handler echoes received multipart post data as json format."""
735
736 if not (self._ShouldHandleRequest("/echomultipartpost") or
737 self._ShouldHandleRequest("/searchbyimage")):
738 return False
739
740 content_type, parameters = cgi.parse_header(
741 self.headers.getheader('content-type'))
742 if content_type == 'multipart/form-data':
743 post_multipart = cgi.parse_multipart(self.rfile, parameters)
744 elif content_type == 'application/x-www-form-urlencoded':
745 raise Exception('POST by application/x-www-form-urlencoded is '
746 'not implemented.')
747 else:
748 post_multipart = {}
749
750 # Since the data can be binary, we encode them by base64.
751 post_multipart_base64_encoded = {}
752 for field, values in post_multipart.items():
753 post_multipart_base64_encoded[field] = [base64.b64encode(value)
754 for value in values]
755
756 result = {'POST_multipart' : post_multipart_base64_encoded}
757
758 self.send_response(200)
759 self.send_header("Content-type", "text/plain")
760 self.end_headers()
761 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
762 return True
763
initial.commit94958cf2008-07-26 22:42:52 +0000764 def DownloadHandler(self):
765 """This handler sends a downloadable file with or without reporting
766 the size (6K)."""
767
768 if self.path.startswith("/download-unknown-size"):
769 send_length = False
770 elif self.path.startswith("/download-known-size"):
771 send_length = True
772 else:
773 return False
774
775 #
776 # The test which uses this functionality is attempting to send
777 # small chunks of data to the client. Use a fairly large buffer
778 # so that we'll fill chrome's IO buffer enough to force it to
779 # actually write the data.
780 # See also the comments in the client-side of this test in
781 # download_uitest.cc
782 #
783 size_chunk1 = 35*1024
784 size_chunk2 = 10*1024
785
786 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000787 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000788 self.send_header('Cache-Control', 'max-age=0')
789 if send_length:
790 self.send_header('Content-Length', size_chunk1 + size_chunk2)
791 self.end_headers()
792
793 # First chunk of data:
794 self.wfile.write("*" * size_chunk1)
795 self.wfile.flush()
796
797 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000798 self.server.wait_for_download = True
799 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000800 self.server.handle_request()
801
802 # Second chunk of data:
803 self.wfile.write("*" * size_chunk2)
804 return True
805
806 def DownloadFinishHandler(self):
807 """This handler just tells the server to finish the current download."""
808
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000809 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000810 return False
811
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000812 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000813 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000814 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000815 self.send_header('Cache-Control', 'max-age=0')
816 self.end_headers()
817 return True
818
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000819 def _ReplaceFileData(self, data, query_parameters):
820 """Replaces matching substrings in a file.
821
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000822 If the 'replace_text' URL query parameter is present, it is expected to be
823 of the form old_text:new_text, which indicates that any old_text strings in
824 the file are replaced with new_text. Multiple 'replace_text' parameters may
825 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000826
827 If the parameters are not present, |data| is returned.
828 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000829
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000830 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000831 replace_text_values = query_dict.get('replace_text', [])
832 for replace_text_value in replace_text_values:
833 replace_text_args = replace_text_value.split(':')
834 if len(replace_text_args) != 2:
835 raise ValueError(
836 'replace_text must be of form old_text:new_text. Actual value: %s' %
837 replace_text_value)
838 old_text_b64, new_text_b64 = replace_text_args
839 old_text = base64.urlsafe_b64decode(old_text_b64)
840 new_text = base64.urlsafe_b64decode(new_text_b64)
841 data = data.replace(old_text, new_text)
842 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000843
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000844 def ZipFileHandler(self):
845 """This handler sends the contents of the requested file in compressed form.
846 Can pass in a parameter that specifies that the content length be
847 C - the compressed size (OK),
848 U - the uncompressed size (Non-standard, but handled),
849 S - less than compressed (OK because we keep going),
850 M - larger than compressed but less than uncompressed (an error),
851 L - larger than uncompressed (an error)
852 Example: compressedfiles/Picture_1.doc?C
853 """
854
855 prefix = "/compressedfiles/"
856 if not self.path.startswith(prefix):
857 return False
858
859 # Consume a request body if present.
860 if self.command == 'POST' or self.command == 'PUT' :
861 self.ReadRequestBody()
862
863 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
864
865 if not query in ('C', 'U', 'S', 'M', 'L'):
866 return False
867
868 sub_path = url_path[len(prefix):]
869 entries = sub_path.split('/')
870 file_path = os.path.join(self.server.data_dir, *entries)
871 if os.path.isdir(file_path):
872 file_path = os.path.join(file_path, 'index.html')
873
874 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000875 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000876 self.send_error(404)
877 return True
878
879 f = open(file_path, "rb")
880 data = f.read()
881 uncompressed_len = len(data)
882 f.close()
883
884 # Compress the data.
885 data = zlib.compress(data)
886 compressed_len = len(data)
887
888 content_length = compressed_len
889 if query == 'U':
890 content_length = uncompressed_len
891 elif query == 'S':
892 content_length = compressed_len / 2
893 elif query == 'M':
894 content_length = (compressed_len + uncompressed_len) / 2
895 elif query == 'L':
896 content_length = compressed_len + uncompressed_len
897
898 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000899 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000900 self.send_header('Content-encoding', 'deflate')
901 self.send_header('Connection', 'close')
902 self.send_header('Content-Length', content_length)
903 self.send_header('ETag', '\'' + file_path + '\'')
904 self.end_headers()
905
906 self.wfile.write(data)
907
908 return True
909
initial.commit94958cf2008-07-26 22:42:52 +0000910 def FileHandler(self):
911 """This handler sends the contents of the requested file. Wow, it's like
912 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000913
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000914 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000915 if not self.path.startswith(prefix):
916 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000917 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000918
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000919 def PostOnlyFileHandler(self):
920 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000921
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000922 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000923 if not self.path.startswith(prefix):
924 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000925 return self._FileHandlerHelper(prefix)
926
927 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000928 request_body = ''
929 if self.command == 'POST' or self.command == 'PUT':
930 # Consume a request body if present.
931 request_body = self.ReadRequestBody()
932
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000933 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000934 query_dict = cgi.parse_qs(query)
935
936 expected_body = query_dict.get('expected_body', [])
937 if expected_body and request_body not in expected_body:
938 self.send_response(404)
939 self.end_headers()
940 self.wfile.write('')
941 return True
942
943 expected_headers = query_dict.get('expected_headers', [])
944 for expected_header in expected_headers:
945 header_name, expected_value = expected_header.split(':')
946 if self.headers.getheader(header_name) != expected_value:
947 self.send_response(404)
948 self.end_headers()
949 self.wfile.write('')
950 return True
951
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000952 sub_path = url_path[len(prefix):]
953 entries = sub_path.split('/')
954 file_path = os.path.join(self.server.data_dir, *entries)
955 if os.path.isdir(file_path):
956 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000957
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000958 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000959 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000960 self.send_error(404)
961 return True
962
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000963 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000964 data = f.read()
965 f.close()
966
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000967 data = self._ReplaceFileData(data, query)
968
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000969 old_protocol_version = self.protocol_version
970
initial.commit94958cf2008-07-26 22:42:52 +0000971 # If file.mock-http-headers exists, it contains the headers we
972 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000973 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000974 if os.path.isfile(headers_path):
975 f = open(headers_path, "r")
976
977 # "HTTP/1.1 200 OK"
978 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000979 http_major, http_minor, status_code = re.findall(
980 'HTTP/(\d+).(\d+) (\d+)', response)[0]
981 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000982 self.send_response(int(status_code))
983
984 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000985 header_values = re.findall('(\S+):\s*(.*)', line)
986 if len(header_values) > 0:
987 # "name: value"
988 name, value = header_values[0]
989 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000990 f.close()
991 else:
992 # Could be more generic once we support mime-type sniffing, but for
993 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000994
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000995 range_header = self.headers.get('Range')
996 if range_header and range_header.startswith('bytes='):
997 # Note this doesn't handle all valid byte range_header values (i.e.
998 # left open ended ones), just enough for what we needed so far.
999 range_header = range_header[6:].split('-')
1000 start = int(range_header[0])
1001 if range_header[1]:
1002 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001003 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001004 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001005
1006 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001007 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1008 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001009 self.send_header('Content-Range', content_range)
1010 data = data[start: end + 1]
1011 else:
1012 self.send_response(200)
1013
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001014 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001015 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001016 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001017 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001018 self.end_headers()
1019
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001020 if (self.command != 'HEAD'):
1021 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001022
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001023 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001024 return True
1025
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001026 def SetCookieHandler(self):
1027 """This handler just sets a cookie, for testing cookie handling."""
1028
1029 if not self._ShouldHandleRequest("/set-cookie"):
1030 return False
1031
1032 query_char = self.path.find('?')
1033 if query_char != -1:
1034 cookie_values = self.path[query_char + 1:].split('&')
1035 else:
1036 cookie_values = ("",)
1037 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001038 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001039 for cookie_value in cookie_values:
1040 self.send_header('Set-Cookie', '%s' % cookie_value)
1041 self.end_headers()
1042 for cookie_value in cookie_values:
1043 self.wfile.write('%s' % cookie_value)
1044 return True
1045
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001046 def SetManyCookiesHandler(self):
1047 """This handler just sets a given number of cookies, for testing handling
1048 of large numbers of cookies."""
1049
1050 if not self._ShouldHandleRequest("/set-many-cookies"):
1051 return False
1052
1053 query_char = self.path.find('?')
1054 if query_char != -1:
1055 num_cookies = int(self.path[query_char + 1:])
1056 else:
1057 num_cookies = 0
1058 self.send_response(200)
1059 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001060 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001061 self.send_header('Set-Cookie', 'a=')
1062 self.end_headers()
1063 self.wfile.write('%d cookies were sent' % num_cookies)
1064 return True
1065
mattm@chromium.org983fc462012-06-30 00:52:08 +00001066 def ExpectAndSetCookieHandler(self):
1067 """Expects some cookies to be sent, and if they are, sets more cookies.
1068
1069 The expect parameter specifies a required cookie. May be specified multiple
1070 times.
1071 The set parameter specifies a cookie to set if all required cookies are
1072 preset. May be specified multiple times.
1073 The data parameter specifies the response body data to be returned."""
1074
1075 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1076 return False
1077
1078 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1079 query_dict = cgi.parse_qs(query)
1080 cookies = set()
1081 if 'Cookie' in self.headers:
1082 cookie_header = self.headers.getheader('Cookie')
1083 cookies.update([s.strip() for s in cookie_header.split(';')])
1084 got_all_expected_cookies = True
1085 for expected_cookie in query_dict.get('expect', []):
1086 if expected_cookie not in cookies:
1087 got_all_expected_cookies = False
1088 self.send_response(200)
1089 self.send_header('Content-Type', 'text/html')
1090 if got_all_expected_cookies:
1091 for cookie_value in query_dict.get('set', []):
1092 self.send_header('Set-Cookie', '%s' % cookie_value)
1093 self.end_headers()
1094 for data_value in query_dict.get('data', []):
1095 self.wfile.write(data_value)
1096 return True
1097
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001098 def SetHeaderHandler(self):
1099 """This handler sets a response header. Parameters are in the
1100 key%3A%20value&key2%3A%20value2 format."""
1101
1102 if not self._ShouldHandleRequest("/set-header"):
1103 return False
1104
1105 query_char = self.path.find('?')
1106 if query_char != -1:
1107 headers_values = self.path[query_char + 1:].split('&')
1108 else:
1109 headers_values = ("",)
1110 self.send_response(200)
1111 self.send_header('Content-Type', 'text/html')
1112 for header_value in headers_values:
1113 header_value = urllib.unquote(header_value)
1114 (key, value) = header_value.split(': ', 1)
1115 self.send_header(key, value)
1116 self.end_headers()
1117 for header_value in headers_values:
1118 self.wfile.write('%s' % header_value)
1119 return True
1120
initial.commit94958cf2008-07-26 22:42:52 +00001121 def AuthBasicHandler(self):
1122 """This handler tests 'Basic' authentication. It just sends a page with
1123 title 'user/pass' if you succeed."""
1124
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001125 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001126 return False
1127
1128 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001129 expected_password = 'secret'
1130 realm = 'testrealm'
1131 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001132
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001133 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1134 query_params = cgi.parse_qs(query, True)
1135 if 'set-cookie-if-challenged' in query_params:
1136 set_cookie_if_challenged = True
1137 if 'password' in query_params:
1138 expected_password = query_params['password'][0]
1139 if 'realm' in query_params:
1140 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001141
initial.commit94958cf2008-07-26 22:42:52 +00001142 auth = self.headers.getheader('authorization')
1143 try:
1144 if not auth:
1145 raise Exception('no auth')
1146 b64str = re.findall(r'Basic (\S+)', auth)[0]
1147 userpass = base64.b64decode(b64str)
1148 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001149 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001150 raise Exception('wrong password')
1151 except Exception, e:
1152 # Authentication failed.
1153 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001154 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001155 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001156 if set_cookie_if_challenged:
1157 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001158 self.end_headers()
1159 self.wfile.write('<html><head>')
1160 self.wfile.write('<title>Denied: %s</title>' % e)
1161 self.wfile.write('</head><body>')
1162 self.wfile.write('auth=%s<p>' % auth)
1163 self.wfile.write('b64str=%s<p>' % b64str)
1164 self.wfile.write('username: %s<p>' % username)
1165 self.wfile.write('userpass: %s<p>' % userpass)
1166 self.wfile.write('password: %s<p>' % password)
1167 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1168 self.wfile.write('</body></html>')
1169 return True
1170
1171 # Authentication successful. (Return a cachable response to allow for
1172 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001173 old_protocol_version = self.protocol_version
1174 self.protocol_version = "HTTP/1.1"
1175
initial.commit94958cf2008-07-26 22:42:52 +00001176 if_none_match = self.headers.getheader('if-none-match')
1177 if if_none_match == "abc":
1178 self.send_response(304)
1179 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001180 elif url_path.endswith(".gif"):
1181 # Using chrome/test/data/google/logo.gif as the test image
1182 test_image_path = ['google', 'logo.gif']
1183 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1184 if not os.path.isfile(gif_path):
1185 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001186 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001187 return True
1188
1189 f = open(gif_path, "rb")
1190 data = f.read()
1191 f.close()
1192
1193 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001194 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001195 self.send_header('Cache-control', 'max-age=60000')
1196 self.send_header('Etag', 'abc')
1197 self.end_headers()
1198 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001199 else:
1200 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001201 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001202 self.send_header('Cache-control', 'max-age=60000')
1203 self.send_header('Etag', 'abc')
1204 self.end_headers()
1205 self.wfile.write('<html><head>')
1206 self.wfile.write('<title>%s/%s</title>' % (username, password))
1207 self.wfile.write('</head><body>')
1208 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001209 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001210 self.wfile.write('</body></html>')
1211
rvargas@google.com54453b72011-05-19 01:11:11 +00001212 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001213 return True
1214
tonyg@chromium.org75054202010-03-31 22:06:10 +00001215 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001216 """Returns a nonce that's stable per request path for the server's lifetime.
1217 This is a fake implementation. A real implementation would only use a given
1218 nonce a single time (hence the name n-once). However, for the purposes of
1219 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001220
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001221 Args:
1222 force_reset: Iff set, the nonce will be changed. Useful for testing the
1223 "stale" response.
1224 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001225
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001226 if force_reset or not self.server.nonce_time:
1227 self.server.nonce_time = time.time()
1228 return hashlib.md5('privatekey%s%d' %
1229 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001230
1231 def AuthDigestHandler(self):
1232 """This handler tests 'Digest' authentication.
1233
1234 It just sends a page with title 'user/pass' if you succeed.
1235
1236 A stale response is sent iff "stale" is present in the request path.
1237 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001238
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001239 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001240 return False
1241
tonyg@chromium.org75054202010-03-31 22:06:10 +00001242 stale = 'stale' in self.path
1243 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001244 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001245 password = 'secret'
1246 realm = 'testrealm'
1247
1248 auth = self.headers.getheader('authorization')
1249 pairs = {}
1250 try:
1251 if not auth:
1252 raise Exception('no auth')
1253 if not auth.startswith('Digest'):
1254 raise Exception('not digest')
1255 # Pull out all the name="value" pairs as a dictionary.
1256 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1257
1258 # Make sure it's all valid.
1259 if pairs['nonce'] != nonce:
1260 raise Exception('wrong nonce')
1261 if pairs['opaque'] != opaque:
1262 raise Exception('wrong opaque')
1263
1264 # Check the 'response' value and make sure it matches our magic hash.
1265 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001266 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001267 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001268 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001269 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001270 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001271 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1272 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001273 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001274
1275 if pairs['response'] != response:
1276 raise Exception('wrong password')
1277 except Exception, e:
1278 # Authentication failed.
1279 self.send_response(401)
1280 hdr = ('Digest '
1281 'realm="%s", '
1282 'domain="/", '
1283 'qop="auth", '
1284 'algorithm=MD5, '
1285 'nonce="%s", '
1286 'opaque="%s"') % (realm, nonce, opaque)
1287 if stale:
1288 hdr += ', stale="TRUE"'
1289 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001290 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001291 self.end_headers()
1292 self.wfile.write('<html><head>')
1293 self.wfile.write('<title>Denied: %s</title>' % e)
1294 self.wfile.write('</head><body>')
1295 self.wfile.write('auth=%s<p>' % auth)
1296 self.wfile.write('pairs=%s<p>' % pairs)
1297 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1298 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1299 self.wfile.write('</body></html>')
1300 return True
1301
1302 # Authentication successful.
1303 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001304 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001305 self.end_headers()
1306 self.wfile.write('<html><head>')
1307 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1308 self.wfile.write('</head><body>')
1309 self.wfile.write('auth=%s<p>' % auth)
1310 self.wfile.write('pairs=%s<p>' % pairs)
1311 self.wfile.write('</body></html>')
1312
1313 return True
1314
1315 def SlowServerHandler(self):
1316 """Wait for the user suggested time before responding. The syntax is
1317 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001318
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001319 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001320 return False
1321 query_char = self.path.find('?')
1322 wait_sec = 1.0
1323 if query_char >= 0:
1324 try:
1325 wait_sec = int(self.path[query_char + 1:])
1326 except ValueError:
1327 pass
1328 time.sleep(wait_sec)
1329 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001330 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001331 self.end_headers()
1332 self.wfile.write("waited %d seconds" % wait_sec)
1333 return True
1334
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001335 def ChunkedServerHandler(self):
1336 """Send chunked response. Allows to specify chunks parameters:
1337 - waitBeforeHeaders - ms to wait before sending headers
1338 - waitBetweenChunks - ms to wait between chunks
1339 - chunkSize - size of each chunk in bytes
1340 - chunksNumber - number of chunks
1341 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1342 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001343
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001344 if not self._ShouldHandleRequest("/chunked"):
1345 return False
1346 query_char = self.path.find('?')
1347 chunkedSettings = {'waitBeforeHeaders' : 0,
1348 'waitBetweenChunks' : 0,
1349 'chunkSize' : 5,
1350 'chunksNumber' : 5}
1351 if query_char >= 0:
1352 params = self.path[query_char + 1:].split('&')
1353 for param in params:
1354 keyValue = param.split('=')
1355 if len(keyValue) == 2:
1356 try:
1357 chunkedSettings[keyValue[0]] = int(keyValue[1])
1358 except ValueError:
1359 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001360 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001361 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1362 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001363 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001364 self.send_header('Connection', 'close')
1365 self.send_header('Transfer-Encoding', 'chunked')
1366 self.end_headers()
1367 # Chunked encoding: sending all chunks, then final zero-length chunk and
1368 # then final CRLF.
1369 for i in range(0, chunkedSettings['chunksNumber']):
1370 if i > 0:
1371 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1372 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001373 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001374 self.sendChunkHelp('')
1375 return True
1376
initial.commit94958cf2008-07-26 22:42:52 +00001377 def ContentTypeHandler(self):
1378 """Returns a string of html with the given content type. E.g.,
1379 /contenttype?text/css returns an html file with the Content-Type
1380 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001381
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001382 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001383 return False
1384 query_char = self.path.find('?')
1385 content_type = self.path[query_char + 1:].strip()
1386 if not content_type:
1387 content_type = 'text/html'
1388 self.send_response(200)
1389 self.send_header('Content-Type', content_type)
1390 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001391 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001392 return True
1393
creis@google.com2f4f6a42011-03-25 19:44:19 +00001394 def NoContentHandler(self):
1395 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001396
creis@google.com2f4f6a42011-03-25 19:44:19 +00001397 if not self._ShouldHandleRequest("/nocontent"):
1398 return False
1399 self.send_response(204)
1400 self.end_headers()
1401 return True
1402
initial.commit94958cf2008-07-26 22:42:52 +00001403 def ServerRedirectHandler(self):
1404 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001405 '/server-redirect?http://foo.bar/asdf' to redirect to
1406 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001407
1408 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001409 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001410 return False
1411
1412 query_char = self.path.find('?')
1413 if query_char < 0 or len(self.path) <= query_char + 1:
1414 self.sendRedirectHelp(test_name)
1415 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001416 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001417
1418 self.send_response(301) # moved permanently
1419 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001420 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001421 self.end_headers()
1422 self.wfile.write('<html><head>')
1423 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1424
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001425 return True
initial.commit94958cf2008-07-26 22:42:52 +00001426
naskoe7a0d0d2014-09-29 08:53:05 -07001427 def CrossSiteRedirectHandler(self):
1428 """Sends a server redirect to the given site. The syntax is
1429 '/cross-site/hostname/...' to redirect to //hostname/...
1430 It is used to navigate between different Sites, causing
1431 cross-site/cross-process navigations in the browser."""
1432
1433 test_name = "/cross-site"
1434 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001435 return False
1436
1437 params = urllib.unquote(self.path[(len(test_name) + 1):])
1438 slash = params.find('/')
1439 if slash < 0:
1440 self.sendRedirectHelp(test_name)
1441 return True
1442
1443 host = params[:slash]
1444 path = params[(slash+1):]
1445 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1446
1447 self.send_response(301) # moved permanently
1448 self.send_header('Location', dest)
1449 self.send_header('Content-Type', 'text/html')
1450 self.end_headers()
1451 self.wfile.write('<html><head>')
1452 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1453
1454 return True
1455
initial.commit94958cf2008-07-26 22:42:52 +00001456 def ClientRedirectHandler(self):
1457 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001458 '/client-redirect?http://foo.bar/asdf' to redirect to
1459 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001460
1461 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001462 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001463 return False
1464
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001465 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001466 if query_char < 0 or len(self.path) <= query_char + 1:
1467 self.sendRedirectHelp(test_name)
1468 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001469 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001470
1471 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001472 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001473 self.end_headers()
1474 self.wfile.write('<html><head>')
1475 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1476 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1477
1478 return True
1479
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001480 def GetSSLSessionCacheHandler(self):
1481 """Send a reply containing a log of the session cache operations."""
1482
1483 if not self._ShouldHandleRequest('/ssl-session-cache'):
1484 return False
1485
1486 self.send_response(200)
1487 self.send_header('Content-Type', 'text/plain')
1488 self.end_headers()
1489 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001490 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001491 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001492 self.wfile.write('Pass --https-record-resume in order to use' +
1493 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001494 return True
1495
1496 for (action, sessionID) in log:
1497 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001498 return True
1499
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001500 def SSLManySmallRecords(self):
1501 """Sends a reply consisting of a variety of small writes. These will be
1502 translated into a series of small SSL records when used over an HTTPS
1503 server."""
1504
1505 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1506 return False
1507
1508 self.send_response(200)
1509 self.send_header('Content-Type', 'text/plain')
1510 self.end_headers()
1511
1512 # Write ~26K of data, in 1350 byte chunks
1513 for i in xrange(20):
1514 self.wfile.write('*' * 1350)
1515 self.wfile.flush()
1516 return True
1517
agl@chromium.org04700be2013-03-02 18:40:41 +00001518 def GetChannelID(self):
1519 """Send a reply containing the hashed ChannelID that the client provided."""
1520
1521 if not self._ShouldHandleRequest('/channel-id'):
1522 return False
1523
1524 self.send_response(200)
1525 self.send_header('Content-Type', 'text/plain')
1526 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001527 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001528 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1529 return True
1530
davidben599e7e72014-09-03 16:19:09 -07001531 def ClientCipherListHandler(self):
1532 """Send a reply containing the cipher suite list that the client
1533 provided. Each cipher suite value is serialized in decimal, followed by a
1534 newline."""
1535
1536 if not self._ShouldHandleRequest('/client-cipher-list'):
1537 return False
1538
1539 self.send_response(200)
1540 self.send_header('Content-Type', 'text/plain')
1541 self.end_headers()
1542
davidben11682512014-10-06 21:09:11 -07001543 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1544 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001545 return True
1546
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001547 def CloseSocketHandler(self):
1548 """Closes the socket without sending anything."""
1549
1550 if not self._ShouldHandleRequest('/close-socket'):
1551 return False
1552
1553 self.wfile.close()
1554 return True
1555
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001556 def RangeResetHandler(self):
1557 """Send data broken up by connection resets every N (default 4K) bytes.
1558 Support range requests. If the data requested doesn't straddle a reset
1559 boundary, it will all be sent. Used for testing resuming downloads."""
1560
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001561 def DataForRange(start, end):
1562 """Data to be provided for a particular range of bytes."""
1563 # Offset and scale to avoid too obvious (and hence potentially
1564 # collidable) data.
1565 return ''.join([chr(y % 256)
1566 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1567
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001568 if not self._ShouldHandleRequest('/rangereset'):
1569 return False
1570
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001571 # HTTP/1.1 is required for ETag and range support.
1572 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001573 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1574
1575 # Defaults
1576 size = 8000
1577 # Note that the rst is sent just before sending the rst_boundary byte.
1578 rst_boundary = 4000
1579 respond_to_range = True
1580 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001581 rst_limit = -1
1582 token = 'DEFAULT'
1583 fail_precondition = 0
1584 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001585
1586 # Parse the query
1587 qdict = urlparse.parse_qs(query, True)
1588 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001589 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001590 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001591 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001592 if 'token' in qdict:
1593 # Identifying token for stateful tests.
1594 token = qdict['token'][0]
1595 if 'rst_limit' in qdict:
1596 # Max number of rsts for a given token.
1597 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001598 if 'bounce_range' in qdict:
1599 respond_to_range = False
1600 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001601 # Note that hold_for_signal will not work with null range requests;
1602 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001603 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001604 if 'no_verifiers' in qdict:
1605 send_verifiers = False
1606 if 'fail_precondition' in qdict:
1607 fail_precondition = int(qdict['fail_precondition'][0])
1608
1609 # Record already set information, or set it.
1610 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1611 if rst_limit != 0:
1612 TestPageHandler.rst_limits[token] -= 1
1613 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1614 token, fail_precondition)
1615 if fail_precondition != 0:
1616 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001617
1618 first_byte = 0
1619 last_byte = size - 1
1620
1621 # Does that define what we want to return, or do we need to apply
1622 # a range?
1623 range_response = False
1624 range_header = self.headers.getheader('range')
1625 if range_header and respond_to_range:
1626 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1627 if mo.group(1):
1628 first_byte = int(mo.group(1))
1629 if mo.group(2):
1630 last_byte = int(mo.group(2))
1631 if last_byte > size - 1:
1632 last_byte = size - 1
1633 range_response = True
1634 if last_byte < first_byte:
1635 return False
1636
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001637 if (fail_precondition and
1638 (self.headers.getheader('If-Modified-Since') or
1639 self.headers.getheader('If-Match'))):
1640 self.send_response(412)
1641 self.end_headers()
1642 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001643
1644 if range_response:
1645 self.send_response(206)
1646 self.send_header('Content-Range',
1647 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1648 else:
1649 self.send_response(200)
1650 self.send_header('Content-Type', 'application/octet-stream')
1651 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001652 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001653 # If fail_precondition is non-zero, then the ETag for each request will be
1654 # different.
1655 etag = "%s%d" % (token, fail_precondition)
1656 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001657 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001658 self.end_headers()
1659
1660 if hold_for_signal:
1661 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1662 # a single byte, the self.server.handle_request() below hangs
1663 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001664 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001665 first_byte = first_byte + 1
1666 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001667 self.server.wait_for_download = True
1668 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001669 self.server.handle_request()
1670
1671 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001672 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001673 # No RST has been requested in this range, so we don't need to
1674 # do anything fancy; just write the data and let the python
1675 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001676 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001677 self.wfile.flush()
1678 return True
1679
1680 # We're resetting the connection part way in; go to the RST
1681 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001682 # Because socket semantics do not guarantee that all the data will be
1683 # sent when using the linger semantics to hard close a socket,
1684 # we send the data and then wait for our peer to release us
1685 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001686 data = DataForRange(first_byte, possible_rst)
1687 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001688 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001689 self.server.wait_for_download = True
1690 while self.server.wait_for_download:
1691 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001692 l_onoff = 1 # Linger is active.
1693 l_linger = 0 # Seconds to linger for.
1694 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1695 struct.pack('ii', l_onoff, l_linger))
1696
1697 # Close all duplicates of the underlying socket to force the RST.
1698 self.wfile.close()
1699 self.rfile.close()
1700 self.connection.close()
1701
1702 return True
1703
initial.commit94958cf2008-07-26 22:42:52 +00001704 def DefaultResponseHandler(self):
1705 """This is the catch-all response handler for requests that aren't handled
1706 by one of the special handlers above.
1707 Note that we specify the content-length as without it the https connection
1708 is not closed properly (and the browser keeps expecting data)."""
1709
1710 contents = "Default response given for path: " + self.path
1711 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001712 self.send_header('Content-Type', 'text/html')
1713 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001714 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001715 if (self.command != 'HEAD'):
1716 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001717 return True
1718
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001719 def RedirectConnectHandler(self):
1720 """Sends a redirect to the CONNECT request for www.redirect.com. This
1721 response is not specified by the RFC, so the browser should not follow
1722 the redirect."""
1723
1724 if (self.path.find("www.redirect.com") < 0):
1725 return False
1726
1727 dest = "http://www.destination.com/foo.js"
1728
1729 self.send_response(302) # moved temporarily
1730 self.send_header('Location', dest)
1731 self.send_header('Connection', 'close')
1732 self.end_headers()
1733 return True
1734
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001735 def ServerAuthConnectHandler(self):
1736 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1737 response doesn't make sense because the proxy server cannot request
1738 server authentication."""
1739
1740 if (self.path.find("www.server-auth.com") < 0):
1741 return False
1742
1743 challenge = 'Basic realm="WallyWorld"'
1744
1745 self.send_response(401) # unauthorized
1746 self.send_header('WWW-Authenticate', challenge)
1747 self.send_header('Connection', 'close')
1748 self.end_headers()
1749 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001750
1751 def DefaultConnectResponseHandler(self):
1752 """This is the catch-all response handler for CONNECT requests that aren't
1753 handled by one of the special handlers above. Real Web servers respond
1754 with 400 to CONNECT requests."""
1755
1756 contents = "Your client has issued a malformed or illegal request."
1757 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001758 self.send_header('Content-Type', 'text/html')
1759 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001760 self.end_headers()
1761 self.wfile.write(contents)
1762 return True
1763
initial.commit94958cf2008-07-26 22:42:52 +00001764 # called by the redirect handling function when there is no parameter
1765 def sendRedirectHelp(self, redirect_name):
1766 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001767 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001768 self.end_headers()
1769 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1770 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1771 self.wfile.write('</body></html>')
1772
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001773 # called by chunked handling function
1774 def sendChunkHelp(self, chunk):
1775 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1776 self.wfile.write('%X\r\n' % len(chunk))
1777 self.wfile.write(chunk)
1778 self.wfile.write('\r\n')
1779
akalin@chromium.org154bb132010-11-12 02:20:27 +00001780
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001781class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001782 def __init__(self, request, client_address, socket_server):
1783 handlers = [self.OCSPResponse]
1784 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001785 testserver_base.BasePageHandler.__init__(self, request, client_address,
1786 socket_server, [], handlers, [],
1787 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001788
1789 def OCSPResponse(self):
1790 self.send_response(200)
1791 self.send_header('Content-Type', 'application/ocsp-response')
1792 self.send_header('Content-Length', str(len(self.ocsp_response)))
1793 self.end_headers()
1794
1795 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001796
mattm@chromium.org830a3712012-11-07 23:00:07 +00001797
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001798class TCPEchoHandler(SocketServer.BaseRequestHandler):
1799 """The RequestHandler class for TCP echo server.
1800
1801 It is instantiated once per connection to the server, and overrides the
1802 handle() method to implement communication to the client.
1803 """
1804
1805 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001806 """Handles the request from the client and constructs a response."""
1807
1808 data = self.request.recv(65536).strip()
1809 # Verify the "echo request" message received from the client. Send back
1810 # "echo response" message if "echo request" message is valid.
1811 try:
1812 return_data = echo_message.GetEchoResponseData(data)
1813 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001814 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001815 except ValueError:
1816 return
1817
1818 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001819
1820
1821class UDPEchoHandler(SocketServer.BaseRequestHandler):
1822 """The RequestHandler class for UDP echo server.
1823
1824 It is instantiated once per connection to the server, and overrides the
1825 handle() method to implement communication to the client.
1826 """
1827
1828 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001829 """Handles the request from the client and constructs a response."""
1830
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001831 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001832 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001833 # Verify the "echo request" message received from the client. Send back
1834 # "echo response" message if "echo request" message is valid.
1835 try:
1836 return_data = echo_message.GetEchoResponseData(data)
1837 if not return_data:
1838 return
1839 except ValueError:
1840 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001841 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001842
1843
bashi@chromium.org33233532012-09-08 17:37:24 +00001844class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1845 """A request handler that behaves as a proxy server which requires
1846 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1847 """
1848
1849 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1850
1851 def parse_request(self):
1852 """Overrides parse_request to check credential."""
1853
1854 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1855 return False
1856
1857 auth = self.headers.getheader('Proxy-Authorization')
1858 if auth != self._AUTH_CREDENTIAL:
1859 self.send_response(407)
1860 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1861 self.end_headers()
1862 return False
1863
1864 return True
1865
1866 def _start_read_write(self, sock):
1867 sock.setblocking(0)
1868 self.request.setblocking(0)
1869 rlist = [self.request, sock]
1870 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001871 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001872 if errors:
1873 self.send_response(500)
1874 self.end_headers()
1875 return
1876 for s in ready_sockets:
1877 received = s.recv(1024)
1878 if len(received) == 0:
1879 return
1880 if s == self.request:
1881 other = sock
1882 else:
1883 other = self.request
1884 other.send(received)
1885
1886 def _do_common_method(self):
1887 url = urlparse.urlparse(self.path)
1888 port = url.port
1889 if not port:
1890 if url.scheme == 'http':
1891 port = 80
1892 elif url.scheme == 'https':
1893 port = 443
1894 if not url.hostname or not port:
1895 self.send_response(400)
1896 self.end_headers()
1897 return
1898
1899 if len(url.path) == 0:
1900 path = '/'
1901 else:
1902 path = url.path
1903 if len(url.query) > 0:
1904 path = '%s?%s' % (url.path, url.query)
1905
1906 sock = None
1907 try:
1908 sock = socket.create_connection((url.hostname, port))
1909 sock.send('%s %s %s\r\n' % (
1910 self.command, path, self.protocol_version))
1911 for header in self.headers.headers:
1912 header = header.strip()
1913 if (header.lower().startswith('connection') or
1914 header.lower().startswith('proxy')):
1915 continue
1916 sock.send('%s\r\n' % header)
1917 sock.send('\r\n')
1918 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001919 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001920 self.send_response(500)
1921 self.end_headers()
1922 finally:
1923 if sock is not None:
1924 sock.close()
1925
1926 def do_CONNECT(self):
1927 try:
1928 pos = self.path.rfind(':')
1929 host = self.path[:pos]
1930 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001931 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001932 self.send_response(400)
1933 self.end_headers()
1934
1935 try:
1936 sock = socket.create_connection((host, port))
1937 self.send_response(200, 'Connection established')
1938 self.end_headers()
1939 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001940 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001941 self.send_response(500)
1942 self.end_headers()
1943 finally:
1944 sock.close()
1945
1946 def do_GET(self):
1947 self._do_common_method()
1948
1949 def do_HEAD(self):
1950 self._do_common_method()
1951
1952
mattm@chromium.org830a3712012-11-07 23:00:07 +00001953class ServerRunner(testserver_base.TestServerRunner):
1954 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001955
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956 def __init__(self):
1957 super(ServerRunner, self).__init__()
1958 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001959
mattm@chromium.org830a3712012-11-07 23:00:07 +00001960 def __make_data_dir(self):
1961 if self.options.data_dir:
1962 if not os.path.isdir(self.options.data_dir):
1963 raise testserver_base.OptionError('specified data dir not found: ' +
1964 self.options.data_dir + ' exiting...')
1965 my_data_dir = self.options.data_dir
1966 else:
1967 # Create the default path to our data dir, relative to the exe dir.
1968 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1969 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001970
mattm@chromium.org830a3712012-11-07 23:00:07 +00001971 #TODO(ibrar): Must use Find* funtion defined in google\tools
1972 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001973
mattm@chromium.org830a3712012-11-07 23:00:07 +00001974 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001975
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 def create_server(self, server_data):
1977 port = self.options.port
1978 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001979
mattm@chromium.org830a3712012-11-07 23:00:07 +00001980 if self.options.server_type == SERVER_HTTP:
1981 if self.options.https:
1982 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001983 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001984 if self.options.cert_and_key_file:
1985 if not os.path.isfile(self.options.cert_and_key_file):
1986 raise testserver_base.OptionError(
1987 'specified server cert file not found: ' +
1988 self.options.cert_and_key_file + ' exiting...')
1989 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001990 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001991 # generate a new certificate and run an OCSP server for it.
1992 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001993 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001994 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001995
mattm@chromium.org830a3712012-11-07 23:00:07 +00001996 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001997
mattm@chromium.org830a3712012-11-07 23:00:07 +00001998 if self.options.ocsp == 'ok':
1999 ocsp_state = minica.OCSP_STATE_GOOD
2000 elif self.options.ocsp == 'revoked':
2001 ocsp_state = minica.OCSP_STATE_REVOKED
2002 elif self.options.ocsp == 'invalid':
2003 ocsp_state = minica.OCSP_STATE_INVALID
2004 elif self.options.ocsp == 'unauthorized':
2005 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2006 elif self.options.ocsp == 'unknown':
2007 ocsp_state = minica.OCSP_STATE_UNKNOWN
2008 else:
2009 raise testserver_base.OptionError('unknown OCSP status: ' +
2010 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002011
mattm@chromium.org830a3712012-11-07 23:00:07 +00002012 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2013 subject = "127.0.0.1",
2014 ocsp_url = ("http://%s:%d/ocsp" %
2015 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002016 ocsp_state = ocsp_state,
2017 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002018
davidben3e2564a2014-11-07 18:51:00 -08002019 if self.options.ocsp_server_unavailable:
2020 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2021 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2022 else:
2023 self.__ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org830a3712012-11-07 23:00:07 +00002024
2025 for ca_cert in self.options.ssl_client_ca:
2026 if not os.path.isfile(ca_cert):
2027 raise testserver_base.OptionError(
2028 'specified trusted client CA file not found: ' + ca_cert +
2029 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002030
2031 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002032 if self.options.staple_ocsp_response:
2033 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002034
mattm@chromium.org830a3712012-11-07 23:00:07 +00002035 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2036 self.options.ssl_client_auth,
2037 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002038 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002039 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002040 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002041 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002042 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002043 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002044 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002045 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002046 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002047 self.options.fallback_scsv,
rsleevi8146efa2015-03-16 12:31:24 -07002048 stapled_ocsp_response)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002049 print 'HTTPS server started on https://%s:%d...' % \
2050 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002051 else:
2052 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002053 print 'HTTP server started on http://%s:%d...' % \
2054 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002055
2056 server.data_dir = self.__make_data_dir()
2057 server.file_root_url = self.options.file_root_url
2058 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002059 elif self.options.server_type == SERVER_WEBSOCKET:
2060 # Launch pywebsocket via WebSocketServer.
2061 logger = logging.getLogger()
2062 logger.addHandler(logging.StreamHandler())
2063 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2064 # is required to work correctly. It should be fixed from pywebsocket side.
2065 os.chdir(self.__make_data_dir())
2066 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002067 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002069 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070 websocket_options.use_tls = True
2071 websocket_options.private_key = self.options.cert_and_key_file
2072 websocket_options.certificate = self.options.cert_and_key_file
2073 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002074 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002075 websocket_options.tls_client_auth = True
2076 if len(self.options.ssl_client_ca) != 1:
2077 raise testserver_base.OptionError(
2078 'one trusted client CA file should be specified')
2079 if not os.path.isfile(self.options.ssl_client_ca[0]):
2080 raise testserver_base.OptionError(
2081 'specified trusted client CA file not found: ' +
2082 self.options.ssl_client_ca[0] + ' exiting...')
2083 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2084 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002085 print 'WebSocket server started on %s://%s:%d...' % \
2086 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002087 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002088 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002089 elif self.options.server_type == SERVER_TCP_ECHO:
2090 # Used for generating the key (randomly) that encodes the "echo request"
2091 # message.
2092 random.seed()
2093 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002094 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 server_data['port'] = server.server_port
2096 elif self.options.server_type == SERVER_UDP_ECHO:
2097 # Used for generating the key (randomly) that encodes the "echo request"
2098 # message.
2099 random.seed()
2100 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002101 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002102 server_data['port'] = server.server_port
2103 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2104 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002105 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002106 server_data['port'] = server.server_port
2107 elif self.options.server_type == SERVER_FTP:
2108 my_data_dir = self.__make_data_dir()
2109
2110 # Instantiate a dummy authorizer for managing 'virtual' users
2111 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2112
2113 # Define a new user having full r/w permissions and a read-only
2114 # anonymous user
2115 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2116
2117 authorizer.add_anonymous(my_data_dir)
2118
2119 # Instantiate FTP handler class
2120 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2121 ftp_handler.authorizer = authorizer
2122
2123 # Define a customized banner (string returned when client connects)
2124 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2125 pyftpdlib.ftpserver.__ver__)
2126
2127 # Instantiate FTP server class and listen to address:port
2128 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2129 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002130 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002131 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002132 raise testserver_base.OptionError('unknown server type' +
2133 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002134
mattm@chromium.org830a3712012-11-07 23:00:07 +00002135 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002136
mattm@chromium.org830a3712012-11-07 23:00:07 +00002137 def run_server(self):
2138 if self.__ocsp_server:
2139 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002140
mattm@chromium.org830a3712012-11-07 23:00:07 +00002141 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002142
mattm@chromium.org830a3712012-11-07 23:00:07 +00002143 if self.__ocsp_server:
2144 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002145
mattm@chromium.org830a3712012-11-07 23:00:07 +00002146 def add_options(self):
2147 testserver_base.TestServerRunner.add_options(self)
2148 self.option_parser.add_option('-f', '--ftp', action='store_const',
2149 const=SERVER_FTP, default=SERVER_HTTP,
2150 dest='server_type',
2151 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002152 self.option_parser.add_option('--tcp-echo', action='store_const',
2153 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2154 dest='server_type',
2155 help='start up a tcp echo server.')
2156 self.option_parser.add_option('--udp-echo', action='store_const',
2157 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2158 dest='server_type',
2159 help='start up a udp echo server.')
2160 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2161 const=SERVER_BASIC_AUTH_PROXY,
2162 default=SERVER_HTTP, dest='server_type',
2163 help='start up a proxy server which requires '
2164 'basic authentication.')
2165 self.option_parser.add_option('--websocket', action='store_const',
2166 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2167 dest='server_type',
2168 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002169 self.option_parser.add_option('--https', action='store_true',
2170 dest='https', help='Specify that https '
2171 'should be used.')
2172 self.option_parser.add_option('--cert-and-key-file',
2173 dest='cert_and_key_file', help='specify the '
2174 'path to the file containing the certificate '
2175 'and private key for the server in PEM '
2176 'format')
2177 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2178 help='The type of OCSP response generated '
2179 'for the automatically generated '
2180 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002181 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2182 default=0, type=int,
2183 help='If non-zero then the generated '
2184 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002185 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2186 default='0', type='int',
2187 help='If nonzero, certain TLS connections '
2188 'will be aborted in order to test version '
2189 'fallback. 1 means all TLS versions will be '
2190 'aborted. 2 means TLS 1.1 or higher will be '
2191 'aborted. 3 means TLS 1.2 or higher will be '
2192 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002193 self.option_parser.add_option('--tls-intolerance-type',
2194 dest='tls_intolerance_type',
2195 default="alert",
2196 help='Controls how the server reacts to a '
2197 'TLS version it is intolerant to. Valid '
2198 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002199 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2200 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002201 default='',
2202 help='Base64 encoded SCT list. If set, '
2203 'server will respond with a '
2204 'signed_certificate_timestamp TLS extension '
2205 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002206 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2207 default=False, const=True,
2208 action='store_const',
2209 help='If given, TLS_FALLBACK_SCSV support '
2210 'will be enabled. This causes the server to '
2211 'reject fallback connections from compatible '
2212 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002213 self.option_parser.add_option('--staple-ocsp-response',
2214 dest='staple_ocsp_response',
2215 default=False, action='store_true',
2216 help='If set, server will staple the OCSP '
2217 'response whenever OCSP is on and the client '
2218 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002219 self.option_parser.add_option('--https-record-resume',
2220 dest='record_resume', const=True,
2221 default=False, action='store_const',
2222 help='Record resumption cache events rather '
2223 'than resuming as normal. Allows the use of '
2224 'the /ssl-session-cache request')
2225 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2226 help='Require SSL client auth on every '
2227 'connection.')
2228 self.option_parser.add_option('--ssl-client-ca', action='append',
2229 default=[], help='Specify that the client '
2230 'certificate request should include the CA '
2231 'named in the subject of the DER-encoded '
2232 'certificate contained in the specified '
2233 'file. This option may appear multiple '
2234 'times, indicating multiple CA names should '
2235 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002236 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2237 default=[], help='Specify that the client '
2238 'certificate request should include the '
2239 'specified certificate_type value. This '
2240 'option may appear multiple times, '
2241 'indicating multiple values should be send '
2242 'in the request. Valid values are '
2243 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2244 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002245 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2246 help='Specify the bulk encryption '
2247 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002248 'SSL server. Valid values are "aes128gcm", '
2249 '"aes256", "aes128", "3des", "rc4". If '
2250 'omitted, all algorithms will be used. This '
2251 'option may appear multiple times, '
2252 'indicating multiple algorithms should be '
2253 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002254 self.option_parser.add_option('--ssl-key-exchange', action='append',
2255 help='Specify the key exchange algorithm(s)'
2256 'that will be accepted by the SSL server. '
2257 'Valid values are "rsa", "dhe_rsa". If '
2258 'omitted, all algorithms will be used. This '
2259 'option may appear multiple times, '
2260 'indicating multiple algorithms should be '
2261 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002262 # TODO(davidben): Add ALPN support to tlslite.
2263 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2264 default=False, const=True,
2265 action='store_const',
2266 help='Enable server support for the NPN '
2267 'extension. The server will advertise '
2268 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002269 self.option_parser.add_option('--file-root-url', default='/files/',
2270 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002271 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2272 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2273 dest='ws_basic_auth',
2274 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002275 self.option_parser.add_option('--ocsp-server-unavailable',
2276 dest='ocsp_server_unavailable',
2277 default=False, action='store_true',
2278 help='If set, the OCSP server will return '
2279 'a tryLater status rather than the actual '
2280 'OCSP response.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002281
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002282
initial.commit94958cf2008-07-26 22:42:52 +00002283if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002284 sys.exit(ServerRunner().main())