blob: d020ca3a86fa47578a3005f85f59534a16fe9602 [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,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +0000160 fallback_scsv_enabled, ocsp_response, disable_session_cache):
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()
196 if ssl_bulk_ciphers is not None:
197 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000198 if ssl_key_exchanges is not None:
199 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000200 if tls_intolerant != 0:
201 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
202 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
initial.commit94958cf2008-07-26 22:42:52 +0000203
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +0000204
205 if disable_session_cache:
206 self.session_cache = None
207 elif record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000208 # If record_resume_info is true then we'll replace the session cache with
209 # an object that records the lookups and inserts that it sees.
210 self.session_cache = RecordingSSLSessionCache()
211 else:
212 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000213 testserver_base.StoppableHTTPServer.__init__(self,
214 server_address,
215 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000216
217 def handshake(self, tlsConnection):
218 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000219
initial.commit94958cf2008-07-26 22:42:52 +0000220 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000221 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000222 tlsConnection.handshakeServer(certChain=self.cert_chain,
223 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000224 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000225 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000226 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000227 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000228 reqCertTypes=self.ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000229 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000230 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000231 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000232 fallbackSCSV=self.fallback_scsv_enabled,
233 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000234 tlsConnection.ignoreAbruptClose = True
235 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000236 except tlslite.api.TLSAbruptCloseError:
237 # Ignore abrupt close.
238 return True
initial.commit94958cf2008-07-26 22:42:52 +0000239 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000240 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000241 return False
242
akalin@chromium.org154bb132010-11-12 02:20:27 +0000243
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000244class FTPServer(testserver_base.ClientRestrictingServerMixIn,
245 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000246 """This is a specialization of FTPServer that adds client verification."""
247
248 pass
249
250
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000251class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
252 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000253 """A TCP echo server that echoes back what it has received."""
254
255 def server_bind(self):
256 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000257
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000258 SocketServer.TCPServer.server_bind(self)
259 host, port = self.socket.getsockname()[:2]
260 self.server_name = socket.getfqdn(host)
261 self.server_port = port
262
263 def serve_forever(self):
264 self.stop = False
265 self.nonce_time = None
266 while not self.stop:
267 self.handle_request()
268 self.socket.close()
269
270
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000271class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
272 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000273 """A UDP echo server that echoes back what it has received."""
274
275 def server_bind(self):
276 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000277
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000278 SocketServer.UDPServer.server_bind(self)
279 host, port = self.socket.getsockname()[:2]
280 self.server_name = socket.getfqdn(host)
281 self.server_port = port
282
283 def serve_forever(self):
284 self.stop = False
285 self.nonce_time = None
286 while not self.stop:
287 self.handle_request()
288 self.socket.close()
289
290
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000291class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000292 # Class variables to allow for persistence state between page handler
293 # invocations
294 rst_limits = {}
295 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000296
297 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000298 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000299 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000300 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000301 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000302 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000303 self.NoCacheMaxAgeTimeHandler,
304 self.NoCacheTimeHandler,
305 self.CacheTimeHandler,
306 self.CacheExpiresHandler,
307 self.CacheProxyRevalidateHandler,
308 self.CachePrivateHandler,
309 self.CachePublicHandler,
310 self.CacheSMaxAgeHandler,
311 self.CacheMustRevalidateHandler,
312 self.CacheMustRevalidateMaxAgeHandler,
313 self.CacheNoStoreHandler,
314 self.CacheNoStoreMaxAgeHandler,
315 self.CacheNoTransformHandler,
316 self.DownloadHandler,
317 self.DownloadFinishHandler,
318 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000319 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000320 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000321 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000322 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000323 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000324 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000325 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000326 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000327 self.AuthBasicHandler,
328 self.AuthDigestHandler,
329 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000330 self.ChunkedServerHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000331 self.ContentTypeHandler,
creis@google.com2f4f6a42011-03-25 19:44:19 +0000332 self.NoContentHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000333 self.ServerRedirectHandler,
334 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',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000360 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000361 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000362 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000363 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000364 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 }
initial.commit94958cf2008-07-26 22:42:52 +0000366 self._default_mime_type = 'text/html'
367
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000368 testserver_base.BasePageHandler.__init__(self, request, client_address,
369 socket_server, connect_handlers,
370 get_handlers, head_handlers,
371 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000372
initial.commit94958cf2008-07-26 22:42:52 +0000373 def GetMIMETypeFromName(self, file_name):
374 """Returns the mime type for the specified file_name. So far it only looks
375 at the file extension."""
376
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000377 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000378 if len(extension) == 0:
379 # no extension.
380 return self._default_mime_type
381
ericroman@google.comc17ca532009-05-07 03:51:05 +0000382 # extension starts with a dot, so we need to remove it
383 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000384
initial.commit94958cf2008-07-26 22:42:52 +0000385 def NoCacheMaxAgeTimeHandler(self):
386 """This request handler yields a page with the title set to the current
387 system time, and no caching requested."""
388
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000389 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000390 return False
391
392 self.send_response(200)
393 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000394 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000395 self.end_headers()
396
maruel@google.come250a9b2009-03-10 17:39:46 +0000397 self.wfile.write('<html><head><title>%s</title></head></html>' %
398 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000399
400 return True
401
402 def NoCacheTimeHandler(self):
403 """This request handler yields a page with the title set to the current
404 system time, and no caching requested."""
405
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000406 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000407 return False
408
409 self.send_response(200)
410 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000411 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000412 self.end_headers()
413
maruel@google.come250a9b2009-03-10 17:39:46 +0000414 self.wfile.write('<html><head><title>%s</title></head></html>' %
415 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000416
417 return True
418
419 def CacheTimeHandler(self):
420 """This request handler yields a page with the title set to the current
421 system time, and allows caching for one minute."""
422
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000423 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000424 return False
425
426 self.send_response(200)
427 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000428 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000429 self.end_headers()
430
maruel@google.come250a9b2009-03-10 17:39:46 +0000431 self.wfile.write('<html><head><title>%s</title></head></html>' %
432 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000433
434 return True
435
436 def CacheExpiresHandler(self):
437 """This request handler yields a page with the title set to the current
438 system time, and set the page to expire on 1 Jan 2099."""
439
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000440 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000441 return False
442
443 self.send_response(200)
444 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000445 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000446 self.end_headers()
447
maruel@google.come250a9b2009-03-10 17:39:46 +0000448 self.wfile.write('<html><head><title>%s</title></head></html>' %
449 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000450
451 return True
452
453 def CacheProxyRevalidateHandler(self):
454 """This request handler yields a page with the title set to the current
455 system time, and allows caching for 60 seconds"""
456
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000457 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000458 return False
459
460 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000461 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000462 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
463 self.end_headers()
464
maruel@google.come250a9b2009-03-10 17:39:46 +0000465 self.wfile.write('<html><head><title>%s</title></head></html>' %
466 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000467
468 return True
469
470 def CachePrivateHandler(self):
471 """This request handler yields a page with the title set to the current
472 system time, and allows caching for 5 seconds."""
473
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000474 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000475 return False
476
477 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000478 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000479 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000480 self.end_headers()
481
maruel@google.come250a9b2009-03-10 17:39:46 +0000482 self.wfile.write('<html><head><title>%s</title></head></html>' %
483 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000484
485 return True
486
487 def CachePublicHandler(self):
488 """This request handler yields a page with the title set to the current
489 system time, and allows caching for 5 seconds."""
490
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000491 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000492 return False
493
494 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000495 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000496 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000497 self.end_headers()
498
maruel@google.come250a9b2009-03-10 17:39:46 +0000499 self.wfile.write('<html><head><title>%s</title></head></html>' %
500 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000501
502 return True
503
504 def CacheSMaxAgeHandler(self):
505 """This request handler yields a page with the title set to the current
506 system time, and does not allow for caching."""
507
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000508 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000509 return False
510
511 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000512 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000513 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
514 self.end_headers()
515
maruel@google.come250a9b2009-03-10 17:39:46 +0000516 self.wfile.write('<html><head><title>%s</title></head></html>' %
517 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000518
519 return True
520
521 def CacheMustRevalidateHandler(self):
522 """This request handler yields a page with the title set to the current
523 system time, and does not allow caching."""
524
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000525 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000526 return False
527
528 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000529 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000530 self.send_header('Cache-Control', 'must-revalidate')
531 self.end_headers()
532
maruel@google.come250a9b2009-03-10 17:39:46 +0000533 self.wfile.write('<html><head><title>%s</title></head></html>' %
534 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000535
536 return True
537
538 def CacheMustRevalidateMaxAgeHandler(self):
539 """This request handler yields a page with the title set to the current
540 system time, and does not allow caching event though max-age of 60
541 seconds is specified."""
542
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000543 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000544 return False
545
546 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000547 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000548 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
549 self.end_headers()
550
maruel@google.come250a9b2009-03-10 17:39:46 +0000551 self.wfile.write('<html><head><title>%s</title></head></html>' %
552 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000553
554 return True
555
initial.commit94958cf2008-07-26 22:42:52 +0000556 def CacheNoStoreHandler(self):
557 """This request handler yields a page with the title set to the current
558 system time, and does not allow the page to be stored."""
559
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000560 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000561 return False
562
563 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000564 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000565 self.send_header('Cache-Control', 'no-store')
566 self.end_headers()
567
maruel@google.come250a9b2009-03-10 17:39:46 +0000568 self.wfile.write('<html><head><title>%s</title></head></html>' %
569 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000570
571 return True
572
573 def CacheNoStoreMaxAgeHandler(self):
574 """This request handler yields a page with the title set to the current
575 system time, and does not allow the page to be stored even though max-age
576 of 60 seconds is specified."""
577
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000578 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000579 return False
580
581 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000582 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000583 self.send_header('Cache-Control', 'max-age=60, no-store')
584 self.end_headers()
585
maruel@google.come250a9b2009-03-10 17:39:46 +0000586 self.wfile.write('<html><head><title>%s</title></head></html>' %
587 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000588
589 return True
590
591
592 def CacheNoTransformHandler(self):
593 """This request handler yields a page with the title set to the current
594 system time, and does not allow the content to transformed during
595 user-agent caching"""
596
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000597 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000598 return False
599
600 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000601 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000602 self.send_header('Cache-Control', 'no-transform')
603 self.end_headers()
604
maruel@google.come250a9b2009-03-10 17:39:46 +0000605 self.wfile.write('<html><head><title>%s</title></head></html>' %
606 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000607
608 return True
609
610 def EchoHeader(self):
611 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000612
ananta@chromium.org219b2062009-10-23 16:09:41 +0000613 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000614
ananta@chromium.org56812d02011-04-07 17:52:05 +0000615 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000616 """This function echoes back the value of a specific request header while
617 allowing caching for 16 hours."""
618
ananta@chromium.org56812d02011-04-07 17:52:05 +0000619 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000620
621 def EchoHeaderHelper(self, echo_header):
622 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000623
ananta@chromium.org219b2062009-10-23 16:09:41 +0000624 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000625 return False
626
627 query_char = self.path.find('?')
628 if query_char != -1:
629 header_name = self.path[query_char+1:]
630
631 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000632 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000633 if echo_header == '/echoheadercache':
634 self.send_header('Cache-control', 'max-age=60000')
635 else:
636 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000637 # insert a vary header to properly indicate that the cachability of this
638 # request is subject to value of the request header being echoed.
639 if len(header_name) > 0:
640 self.send_header('Vary', header_name)
641 self.end_headers()
642
643 if len(header_name) > 0:
644 self.wfile.write(self.headers.getheader(header_name))
645
646 return True
647
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000648 def ReadRequestBody(self):
649 """This function reads the body of the current HTTP request, handling
650 both plain and chunked transfer encoded requests."""
651
652 if self.headers.getheader('transfer-encoding') != 'chunked':
653 length = int(self.headers.getheader('content-length'))
654 return self.rfile.read(length)
655
656 # Read the request body as chunks.
657 body = ""
658 while True:
659 line = self.rfile.readline()
660 length = int(line, 16)
661 if length == 0:
662 self.rfile.readline()
663 break
664 body += self.rfile.read(length)
665 self.rfile.read(2)
666 return body
667
initial.commit94958cf2008-07-26 22:42:52 +0000668 def EchoHandler(self):
669 """This handler just echoes back the payload of the request, for testing
670 form submission."""
671
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000672 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000673 return False
674
675 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000676 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000677 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000678 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000679 return True
680
681 def EchoTitleHandler(self):
682 """This handler is like Echo, but sets the page title to the request."""
683
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000684 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000685 return False
686
687 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000688 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000689 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000690 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000691 self.wfile.write('<html><head><title>')
692 self.wfile.write(request)
693 self.wfile.write('</title></head></html>')
694 return True
695
696 def EchoAllHandler(self):
697 """This handler yields a (more) human-readable page listing information
698 about the request header & contents."""
699
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000700 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000701 return False
702
703 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000704 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000705 self.end_headers()
706 self.wfile.write('<html><head><style>'
707 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
708 '</style></head><body>'
709 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000710 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000711 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000712
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000713 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000714 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000715 params = cgi.parse_qs(qs, keep_blank_values=1)
716
717 for param in params:
718 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000719
720 self.wfile.write('</pre>')
721
722 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
723
724 self.wfile.write('</body></html>')
725 return True
726
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000727 def EchoMultipartPostHandler(self):
728 """This handler echoes received multipart post data as json format."""
729
730 if not (self._ShouldHandleRequest("/echomultipartpost") or
731 self._ShouldHandleRequest("/searchbyimage")):
732 return False
733
734 content_type, parameters = cgi.parse_header(
735 self.headers.getheader('content-type'))
736 if content_type == 'multipart/form-data':
737 post_multipart = cgi.parse_multipart(self.rfile, parameters)
738 elif content_type == 'application/x-www-form-urlencoded':
739 raise Exception('POST by application/x-www-form-urlencoded is '
740 'not implemented.')
741 else:
742 post_multipart = {}
743
744 # Since the data can be binary, we encode them by base64.
745 post_multipart_base64_encoded = {}
746 for field, values in post_multipart.items():
747 post_multipart_base64_encoded[field] = [base64.b64encode(value)
748 for value in values]
749
750 result = {'POST_multipart' : post_multipart_base64_encoded}
751
752 self.send_response(200)
753 self.send_header("Content-type", "text/plain")
754 self.end_headers()
755 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
756 return True
757
initial.commit94958cf2008-07-26 22:42:52 +0000758 def DownloadHandler(self):
759 """This handler sends a downloadable file with or without reporting
760 the size (6K)."""
761
762 if self.path.startswith("/download-unknown-size"):
763 send_length = False
764 elif self.path.startswith("/download-known-size"):
765 send_length = True
766 else:
767 return False
768
769 #
770 # The test which uses this functionality is attempting to send
771 # small chunks of data to the client. Use a fairly large buffer
772 # so that we'll fill chrome's IO buffer enough to force it to
773 # actually write the data.
774 # See also the comments in the client-side of this test in
775 # download_uitest.cc
776 #
777 size_chunk1 = 35*1024
778 size_chunk2 = 10*1024
779
780 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000781 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000782 self.send_header('Cache-Control', 'max-age=0')
783 if send_length:
784 self.send_header('Content-Length', size_chunk1 + size_chunk2)
785 self.end_headers()
786
787 # First chunk of data:
788 self.wfile.write("*" * size_chunk1)
789 self.wfile.flush()
790
791 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000792 self.server.wait_for_download = True
793 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000794 self.server.handle_request()
795
796 # Second chunk of data:
797 self.wfile.write("*" * size_chunk2)
798 return True
799
800 def DownloadFinishHandler(self):
801 """This handler just tells the server to finish the current download."""
802
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000803 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000804 return False
805
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000806 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000807 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000808 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000809 self.send_header('Cache-Control', 'max-age=0')
810 self.end_headers()
811 return True
812
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000813 def _ReplaceFileData(self, data, query_parameters):
814 """Replaces matching substrings in a file.
815
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000816 If the 'replace_text' URL query parameter is present, it is expected to be
817 of the form old_text:new_text, which indicates that any old_text strings in
818 the file are replaced with new_text. Multiple 'replace_text' parameters may
819 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000820
821 If the parameters are not present, |data| is returned.
822 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000823
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000824 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000825 replace_text_values = query_dict.get('replace_text', [])
826 for replace_text_value in replace_text_values:
827 replace_text_args = replace_text_value.split(':')
828 if len(replace_text_args) != 2:
829 raise ValueError(
830 'replace_text must be of form old_text:new_text. Actual value: %s' %
831 replace_text_value)
832 old_text_b64, new_text_b64 = replace_text_args
833 old_text = base64.urlsafe_b64decode(old_text_b64)
834 new_text = base64.urlsafe_b64decode(new_text_b64)
835 data = data.replace(old_text, new_text)
836 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000837
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000838 def ZipFileHandler(self):
839 """This handler sends the contents of the requested file in compressed form.
840 Can pass in a parameter that specifies that the content length be
841 C - the compressed size (OK),
842 U - the uncompressed size (Non-standard, but handled),
843 S - less than compressed (OK because we keep going),
844 M - larger than compressed but less than uncompressed (an error),
845 L - larger than uncompressed (an error)
846 Example: compressedfiles/Picture_1.doc?C
847 """
848
849 prefix = "/compressedfiles/"
850 if not self.path.startswith(prefix):
851 return False
852
853 # Consume a request body if present.
854 if self.command == 'POST' or self.command == 'PUT' :
855 self.ReadRequestBody()
856
857 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
858
859 if not query in ('C', 'U', 'S', 'M', 'L'):
860 return False
861
862 sub_path = url_path[len(prefix):]
863 entries = sub_path.split('/')
864 file_path = os.path.join(self.server.data_dir, *entries)
865 if os.path.isdir(file_path):
866 file_path = os.path.join(file_path, 'index.html')
867
868 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000869 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000870 self.send_error(404)
871 return True
872
873 f = open(file_path, "rb")
874 data = f.read()
875 uncompressed_len = len(data)
876 f.close()
877
878 # Compress the data.
879 data = zlib.compress(data)
880 compressed_len = len(data)
881
882 content_length = compressed_len
883 if query == 'U':
884 content_length = uncompressed_len
885 elif query == 'S':
886 content_length = compressed_len / 2
887 elif query == 'M':
888 content_length = (compressed_len + uncompressed_len) / 2
889 elif query == 'L':
890 content_length = compressed_len + uncompressed_len
891
892 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000893 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000894 self.send_header('Content-encoding', 'deflate')
895 self.send_header('Connection', 'close')
896 self.send_header('Content-Length', content_length)
897 self.send_header('ETag', '\'' + file_path + '\'')
898 self.end_headers()
899
900 self.wfile.write(data)
901
902 return True
903
initial.commit94958cf2008-07-26 22:42:52 +0000904 def FileHandler(self):
905 """This handler sends the contents of the requested file. Wow, it's like
906 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000907
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000908 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000909 if not self.path.startswith(prefix):
910 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000911 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000912
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000913 def PostOnlyFileHandler(self):
914 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000915
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000916 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000917 if not self.path.startswith(prefix):
918 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000919 return self._FileHandlerHelper(prefix)
920
921 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000922 request_body = ''
923 if self.command == 'POST' or self.command == 'PUT':
924 # Consume a request body if present.
925 request_body = self.ReadRequestBody()
926
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000927 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000928 query_dict = cgi.parse_qs(query)
929
930 expected_body = query_dict.get('expected_body', [])
931 if expected_body and request_body not in expected_body:
932 self.send_response(404)
933 self.end_headers()
934 self.wfile.write('')
935 return True
936
937 expected_headers = query_dict.get('expected_headers', [])
938 for expected_header in expected_headers:
939 header_name, expected_value = expected_header.split(':')
940 if self.headers.getheader(header_name) != expected_value:
941 self.send_response(404)
942 self.end_headers()
943 self.wfile.write('')
944 return True
945
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000946 sub_path = url_path[len(prefix):]
947 entries = sub_path.split('/')
948 file_path = os.path.join(self.server.data_dir, *entries)
949 if os.path.isdir(file_path):
950 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000951
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000952 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000953 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000954 self.send_error(404)
955 return True
956
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000957 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000958 data = f.read()
959 f.close()
960
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000961 data = self._ReplaceFileData(data, query)
962
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000963 old_protocol_version = self.protocol_version
964
initial.commit94958cf2008-07-26 22:42:52 +0000965 # If file.mock-http-headers exists, it contains the headers we
966 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000967 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000968 if os.path.isfile(headers_path):
969 f = open(headers_path, "r")
970
971 # "HTTP/1.1 200 OK"
972 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000973 http_major, http_minor, status_code = re.findall(
974 'HTTP/(\d+).(\d+) (\d+)', response)[0]
975 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000976 self.send_response(int(status_code))
977
978 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000979 header_values = re.findall('(\S+):\s*(.*)', line)
980 if len(header_values) > 0:
981 # "name: value"
982 name, value = header_values[0]
983 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000984 f.close()
985 else:
986 # Could be more generic once we support mime-type sniffing, but for
987 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000988
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000989 range_header = self.headers.get('Range')
990 if range_header and range_header.startswith('bytes='):
991 # Note this doesn't handle all valid byte range_header values (i.e.
992 # left open ended ones), just enough for what we needed so far.
993 range_header = range_header[6:].split('-')
994 start = int(range_header[0])
995 if range_header[1]:
996 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000997 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000998 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +0000999
1000 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001001 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1002 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001003 self.send_header('Content-Range', content_range)
1004 data = data[start: end + 1]
1005 else:
1006 self.send_response(200)
1007
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001008 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001009 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001010 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001011 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001012 self.end_headers()
1013
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001014 if (self.command != 'HEAD'):
1015 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001016
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001017 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001018 return True
1019
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001020 def SetCookieHandler(self):
1021 """This handler just sets a cookie, for testing cookie handling."""
1022
1023 if not self._ShouldHandleRequest("/set-cookie"):
1024 return False
1025
1026 query_char = self.path.find('?')
1027 if query_char != -1:
1028 cookie_values = self.path[query_char + 1:].split('&')
1029 else:
1030 cookie_values = ("",)
1031 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001032 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001033 for cookie_value in cookie_values:
1034 self.send_header('Set-Cookie', '%s' % cookie_value)
1035 self.end_headers()
1036 for cookie_value in cookie_values:
1037 self.wfile.write('%s' % cookie_value)
1038 return True
1039
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001040 def SetManyCookiesHandler(self):
1041 """This handler just sets a given number of cookies, for testing handling
1042 of large numbers of cookies."""
1043
1044 if not self._ShouldHandleRequest("/set-many-cookies"):
1045 return False
1046
1047 query_char = self.path.find('?')
1048 if query_char != -1:
1049 num_cookies = int(self.path[query_char + 1:])
1050 else:
1051 num_cookies = 0
1052 self.send_response(200)
1053 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001054 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001055 self.send_header('Set-Cookie', 'a=')
1056 self.end_headers()
1057 self.wfile.write('%d cookies were sent' % num_cookies)
1058 return True
1059
mattm@chromium.org983fc462012-06-30 00:52:08 +00001060 def ExpectAndSetCookieHandler(self):
1061 """Expects some cookies to be sent, and if they are, sets more cookies.
1062
1063 The expect parameter specifies a required cookie. May be specified multiple
1064 times.
1065 The set parameter specifies a cookie to set if all required cookies are
1066 preset. May be specified multiple times.
1067 The data parameter specifies the response body data to be returned."""
1068
1069 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1070 return False
1071
1072 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1073 query_dict = cgi.parse_qs(query)
1074 cookies = set()
1075 if 'Cookie' in self.headers:
1076 cookie_header = self.headers.getheader('Cookie')
1077 cookies.update([s.strip() for s in cookie_header.split(';')])
1078 got_all_expected_cookies = True
1079 for expected_cookie in query_dict.get('expect', []):
1080 if expected_cookie not in cookies:
1081 got_all_expected_cookies = False
1082 self.send_response(200)
1083 self.send_header('Content-Type', 'text/html')
1084 if got_all_expected_cookies:
1085 for cookie_value in query_dict.get('set', []):
1086 self.send_header('Set-Cookie', '%s' % cookie_value)
1087 self.end_headers()
1088 for data_value in query_dict.get('data', []):
1089 self.wfile.write(data_value)
1090 return True
1091
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001092 def SetHeaderHandler(self):
1093 """This handler sets a response header. Parameters are in the
1094 key%3A%20value&key2%3A%20value2 format."""
1095
1096 if not self._ShouldHandleRequest("/set-header"):
1097 return False
1098
1099 query_char = self.path.find('?')
1100 if query_char != -1:
1101 headers_values = self.path[query_char + 1:].split('&')
1102 else:
1103 headers_values = ("",)
1104 self.send_response(200)
1105 self.send_header('Content-Type', 'text/html')
1106 for header_value in headers_values:
1107 header_value = urllib.unquote(header_value)
1108 (key, value) = header_value.split(': ', 1)
1109 self.send_header(key, value)
1110 self.end_headers()
1111 for header_value in headers_values:
1112 self.wfile.write('%s' % header_value)
1113 return True
1114
initial.commit94958cf2008-07-26 22:42:52 +00001115 def AuthBasicHandler(self):
1116 """This handler tests 'Basic' authentication. It just sends a page with
1117 title 'user/pass' if you succeed."""
1118
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001119 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001120 return False
1121
1122 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001123 expected_password = 'secret'
1124 realm = 'testrealm'
1125 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001126
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001127 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1128 query_params = cgi.parse_qs(query, True)
1129 if 'set-cookie-if-challenged' in query_params:
1130 set_cookie_if_challenged = True
1131 if 'password' in query_params:
1132 expected_password = query_params['password'][0]
1133 if 'realm' in query_params:
1134 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001135
initial.commit94958cf2008-07-26 22:42:52 +00001136 auth = self.headers.getheader('authorization')
1137 try:
1138 if not auth:
1139 raise Exception('no auth')
1140 b64str = re.findall(r'Basic (\S+)', auth)[0]
1141 userpass = base64.b64decode(b64str)
1142 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001143 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001144 raise Exception('wrong password')
1145 except Exception, e:
1146 # Authentication failed.
1147 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001148 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001149 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001150 if set_cookie_if_challenged:
1151 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001152 self.end_headers()
1153 self.wfile.write('<html><head>')
1154 self.wfile.write('<title>Denied: %s</title>' % e)
1155 self.wfile.write('</head><body>')
1156 self.wfile.write('auth=%s<p>' % auth)
1157 self.wfile.write('b64str=%s<p>' % b64str)
1158 self.wfile.write('username: %s<p>' % username)
1159 self.wfile.write('userpass: %s<p>' % userpass)
1160 self.wfile.write('password: %s<p>' % password)
1161 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1162 self.wfile.write('</body></html>')
1163 return True
1164
1165 # Authentication successful. (Return a cachable response to allow for
1166 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001167 old_protocol_version = self.protocol_version
1168 self.protocol_version = "HTTP/1.1"
1169
initial.commit94958cf2008-07-26 22:42:52 +00001170 if_none_match = self.headers.getheader('if-none-match')
1171 if if_none_match == "abc":
1172 self.send_response(304)
1173 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001174 elif url_path.endswith(".gif"):
1175 # Using chrome/test/data/google/logo.gif as the test image
1176 test_image_path = ['google', 'logo.gif']
1177 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1178 if not os.path.isfile(gif_path):
1179 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001180 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001181 return True
1182
1183 f = open(gif_path, "rb")
1184 data = f.read()
1185 f.close()
1186
1187 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001188 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001189 self.send_header('Cache-control', 'max-age=60000')
1190 self.send_header('Etag', 'abc')
1191 self.end_headers()
1192 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001193 else:
1194 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001195 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001196 self.send_header('Cache-control', 'max-age=60000')
1197 self.send_header('Etag', 'abc')
1198 self.end_headers()
1199 self.wfile.write('<html><head>')
1200 self.wfile.write('<title>%s/%s</title>' % (username, password))
1201 self.wfile.write('</head><body>')
1202 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001203 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001204 self.wfile.write('</body></html>')
1205
rvargas@google.com54453b72011-05-19 01:11:11 +00001206 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001207 return True
1208
tonyg@chromium.org75054202010-03-31 22:06:10 +00001209 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001210 """Returns a nonce that's stable per request path for the server's lifetime.
1211 This is a fake implementation. A real implementation would only use a given
1212 nonce a single time (hence the name n-once). However, for the purposes of
1213 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001214
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001215 Args:
1216 force_reset: Iff set, the nonce will be changed. Useful for testing the
1217 "stale" response.
1218 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001219
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001220 if force_reset or not self.server.nonce_time:
1221 self.server.nonce_time = time.time()
1222 return hashlib.md5('privatekey%s%d' %
1223 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001224
1225 def AuthDigestHandler(self):
1226 """This handler tests 'Digest' authentication.
1227
1228 It just sends a page with title 'user/pass' if you succeed.
1229
1230 A stale response is sent iff "stale" is present in the request path.
1231 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001232
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001233 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001234 return False
1235
tonyg@chromium.org75054202010-03-31 22:06:10 +00001236 stale = 'stale' in self.path
1237 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001238 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001239 password = 'secret'
1240 realm = 'testrealm'
1241
1242 auth = self.headers.getheader('authorization')
1243 pairs = {}
1244 try:
1245 if not auth:
1246 raise Exception('no auth')
1247 if not auth.startswith('Digest'):
1248 raise Exception('not digest')
1249 # Pull out all the name="value" pairs as a dictionary.
1250 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1251
1252 # Make sure it's all valid.
1253 if pairs['nonce'] != nonce:
1254 raise Exception('wrong nonce')
1255 if pairs['opaque'] != opaque:
1256 raise Exception('wrong opaque')
1257
1258 # Check the 'response' value and make sure it matches our magic hash.
1259 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001260 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001261 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001262 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001263 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001264 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001265 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1266 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001267 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001268
1269 if pairs['response'] != response:
1270 raise Exception('wrong password')
1271 except Exception, e:
1272 # Authentication failed.
1273 self.send_response(401)
1274 hdr = ('Digest '
1275 'realm="%s", '
1276 'domain="/", '
1277 'qop="auth", '
1278 'algorithm=MD5, '
1279 'nonce="%s", '
1280 'opaque="%s"') % (realm, nonce, opaque)
1281 if stale:
1282 hdr += ', stale="TRUE"'
1283 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001284 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001285 self.end_headers()
1286 self.wfile.write('<html><head>')
1287 self.wfile.write('<title>Denied: %s</title>' % e)
1288 self.wfile.write('</head><body>')
1289 self.wfile.write('auth=%s<p>' % auth)
1290 self.wfile.write('pairs=%s<p>' % pairs)
1291 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1292 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1293 self.wfile.write('</body></html>')
1294 return True
1295
1296 # Authentication successful.
1297 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001298 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001299 self.end_headers()
1300 self.wfile.write('<html><head>')
1301 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1302 self.wfile.write('</head><body>')
1303 self.wfile.write('auth=%s<p>' % auth)
1304 self.wfile.write('pairs=%s<p>' % pairs)
1305 self.wfile.write('</body></html>')
1306
1307 return True
1308
1309 def SlowServerHandler(self):
1310 """Wait for the user suggested time before responding. The syntax is
1311 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001312
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001313 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001314 return False
1315 query_char = self.path.find('?')
1316 wait_sec = 1.0
1317 if query_char >= 0:
1318 try:
1319 wait_sec = int(self.path[query_char + 1:])
1320 except ValueError:
1321 pass
1322 time.sleep(wait_sec)
1323 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001324 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001325 self.end_headers()
1326 self.wfile.write("waited %d seconds" % wait_sec)
1327 return True
1328
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001329 def ChunkedServerHandler(self):
1330 """Send chunked response. Allows to specify chunks parameters:
1331 - waitBeforeHeaders - ms to wait before sending headers
1332 - waitBetweenChunks - ms to wait between chunks
1333 - chunkSize - size of each chunk in bytes
1334 - chunksNumber - number of chunks
1335 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1336 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001337
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001338 if not self._ShouldHandleRequest("/chunked"):
1339 return False
1340 query_char = self.path.find('?')
1341 chunkedSettings = {'waitBeforeHeaders' : 0,
1342 'waitBetweenChunks' : 0,
1343 'chunkSize' : 5,
1344 'chunksNumber' : 5}
1345 if query_char >= 0:
1346 params = self.path[query_char + 1:].split('&')
1347 for param in params:
1348 keyValue = param.split('=')
1349 if len(keyValue) == 2:
1350 try:
1351 chunkedSettings[keyValue[0]] = int(keyValue[1])
1352 except ValueError:
1353 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001354 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001355 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1356 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001357 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001358 self.send_header('Connection', 'close')
1359 self.send_header('Transfer-Encoding', 'chunked')
1360 self.end_headers()
1361 # Chunked encoding: sending all chunks, then final zero-length chunk and
1362 # then final CRLF.
1363 for i in range(0, chunkedSettings['chunksNumber']):
1364 if i > 0:
1365 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1366 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001367 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001368 self.sendChunkHelp('')
1369 return True
1370
initial.commit94958cf2008-07-26 22:42:52 +00001371 def ContentTypeHandler(self):
1372 """Returns a string of html with the given content type. E.g.,
1373 /contenttype?text/css returns an html file with the Content-Type
1374 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001375
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001376 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001377 return False
1378 query_char = self.path.find('?')
1379 content_type = self.path[query_char + 1:].strip()
1380 if not content_type:
1381 content_type = 'text/html'
1382 self.send_response(200)
1383 self.send_header('Content-Type', content_type)
1384 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001385 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001386 return True
1387
creis@google.com2f4f6a42011-03-25 19:44:19 +00001388 def NoContentHandler(self):
1389 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001390
creis@google.com2f4f6a42011-03-25 19:44:19 +00001391 if not self._ShouldHandleRequest("/nocontent"):
1392 return False
1393 self.send_response(204)
1394 self.end_headers()
1395 return True
1396
initial.commit94958cf2008-07-26 22:42:52 +00001397 def ServerRedirectHandler(self):
1398 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001399 '/server-redirect?http://foo.bar/asdf' to redirect to
1400 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001401
1402 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001403 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001404 return False
1405
1406 query_char = self.path.find('?')
1407 if query_char < 0 or len(self.path) <= query_char + 1:
1408 self.sendRedirectHelp(test_name)
1409 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001410 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001411
1412 self.send_response(301) # moved permanently
1413 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001414 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001415 self.end_headers()
1416 self.wfile.write('<html><head>')
1417 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1418
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001419 return True
initial.commit94958cf2008-07-26 22:42:52 +00001420
1421 def ClientRedirectHandler(self):
1422 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001423 '/client-redirect?http://foo.bar/asdf' to redirect to
1424 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001425
1426 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001427 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001428 return False
1429
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001430 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001431 if query_char < 0 or len(self.path) <= query_char + 1:
1432 self.sendRedirectHelp(test_name)
1433 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001434 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001435
1436 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001437 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001438 self.end_headers()
1439 self.wfile.write('<html><head>')
1440 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1441 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1442
1443 return True
1444
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001445 def GetSSLSessionCacheHandler(self):
1446 """Send a reply containing a log of the session cache operations."""
1447
1448 if not self._ShouldHandleRequest('/ssl-session-cache'):
1449 return False
1450
1451 self.send_response(200)
1452 self.send_header('Content-Type', 'text/plain')
1453 self.end_headers()
1454 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001455 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001456 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001457 self.wfile.write('Pass --https-record-resume in order to use' +
1458 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001459 return True
1460
1461 for (action, sessionID) in log:
1462 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001463 return True
1464
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001465 def SSLManySmallRecords(self):
1466 """Sends a reply consisting of a variety of small writes. These will be
1467 translated into a series of small SSL records when used over an HTTPS
1468 server."""
1469
1470 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1471 return False
1472
1473 self.send_response(200)
1474 self.send_header('Content-Type', 'text/plain')
1475 self.end_headers()
1476
1477 # Write ~26K of data, in 1350 byte chunks
1478 for i in xrange(20):
1479 self.wfile.write('*' * 1350)
1480 self.wfile.flush()
1481 return True
1482
agl@chromium.org04700be2013-03-02 18:40:41 +00001483 def GetChannelID(self):
1484 """Send a reply containing the hashed ChannelID that the client provided."""
1485
1486 if not self._ShouldHandleRequest('/channel-id'):
1487 return False
1488
1489 self.send_response(200)
1490 self.send_header('Content-Type', 'text/plain')
1491 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001492 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001493 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1494 return True
1495
davidben599e7e72014-09-03 16:19:09 -07001496 def ClientCipherListHandler(self):
1497 """Send a reply containing the cipher suite list that the client
1498 provided. Each cipher suite value is serialized in decimal, followed by a
1499 newline."""
1500
1501 if not self._ShouldHandleRequest('/client-cipher-list'):
1502 return False
1503
1504 self.send_response(200)
1505 self.send_header('Content-Type', 'text/plain')
1506 self.end_headers()
1507
1508 for cipher_suite in self.server.tlsConnection.clientHello.cipher_suites:
1509 self.wfile.write(str(cipher_suite))
1510 self.wfile.write('\n')
1511 return True
1512
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001513 def CloseSocketHandler(self):
1514 """Closes the socket without sending anything."""
1515
1516 if not self._ShouldHandleRequest('/close-socket'):
1517 return False
1518
1519 self.wfile.close()
1520 return True
1521
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001522 def RangeResetHandler(self):
1523 """Send data broken up by connection resets every N (default 4K) bytes.
1524 Support range requests. If the data requested doesn't straddle a reset
1525 boundary, it will all be sent. Used for testing resuming downloads."""
1526
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001527 def DataForRange(start, end):
1528 """Data to be provided for a particular range of bytes."""
1529 # Offset and scale to avoid too obvious (and hence potentially
1530 # collidable) data.
1531 return ''.join([chr(y % 256)
1532 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1533
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001534 if not self._ShouldHandleRequest('/rangereset'):
1535 return False
1536
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001537 # HTTP/1.1 is required for ETag and range support.
1538 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001539 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1540
1541 # Defaults
1542 size = 8000
1543 # Note that the rst is sent just before sending the rst_boundary byte.
1544 rst_boundary = 4000
1545 respond_to_range = True
1546 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001547 rst_limit = -1
1548 token = 'DEFAULT'
1549 fail_precondition = 0
1550 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001551
1552 # Parse the query
1553 qdict = urlparse.parse_qs(query, True)
1554 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001555 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001556 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001557 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001558 if 'token' in qdict:
1559 # Identifying token for stateful tests.
1560 token = qdict['token'][0]
1561 if 'rst_limit' in qdict:
1562 # Max number of rsts for a given token.
1563 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001564 if 'bounce_range' in qdict:
1565 respond_to_range = False
1566 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001567 # Note that hold_for_signal will not work with null range requests;
1568 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001569 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001570 if 'no_verifiers' in qdict:
1571 send_verifiers = False
1572 if 'fail_precondition' in qdict:
1573 fail_precondition = int(qdict['fail_precondition'][0])
1574
1575 # Record already set information, or set it.
1576 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1577 if rst_limit != 0:
1578 TestPageHandler.rst_limits[token] -= 1
1579 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1580 token, fail_precondition)
1581 if fail_precondition != 0:
1582 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001583
1584 first_byte = 0
1585 last_byte = size - 1
1586
1587 # Does that define what we want to return, or do we need to apply
1588 # a range?
1589 range_response = False
1590 range_header = self.headers.getheader('range')
1591 if range_header and respond_to_range:
1592 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1593 if mo.group(1):
1594 first_byte = int(mo.group(1))
1595 if mo.group(2):
1596 last_byte = int(mo.group(2))
1597 if last_byte > size - 1:
1598 last_byte = size - 1
1599 range_response = True
1600 if last_byte < first_byte:
1601 return False
1602
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001603 if (fail_precondition and
1604 (self.headers.getheader('If-Modified-Since') or
1605 self.headers.getheader('If-Match'))):
1606 self.send_response(412)
1607 self.end_headers()
1608 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001609
1610 if range_response:
1611 self.send_response(206)
1612 self.send_header('Content-Range',
1613 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1614 else:
1615 self.send_response(200)
1616 self.send_header('Content-Type', 'application/octet-stream')
1617 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001618 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001619 # If fail_precondition is non-zero, then the ETag for each request will be
1620 # different.
1621 etag = "%s%d" % (token, fail_precondition)
1622 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001623 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001624 self.end_headers()
1625
1626 if hold_for_signal:
1627 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1628 # a single byte, the self.server.handle_request() below hangs
1629 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001630 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001631 first_byte = first_byte + 1
1632 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001633 self.server.wait_for_download = True
1634 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001635 self.server.handle_request()
1636
1637 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001638 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001639 # No RST has been requested in this range, so we don't need to
1640 # do anything fancy; just write the data and let the python
1641 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001642 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001643 self.wfile.flush()
1644 return True
1645
1646 # We're resetting the connection part way in; go to the RST
1647 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001648 # Because socket semantics do not guarantee that all the data will be
1649 # sent when using the linger semantics to hard close a socket,
1650 # we send the data and then wait for our peer to release us
1651 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001652 data = DataForRange(first_byte, possible_rst)
1653 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001654 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001655 self.server.wait_for_download = True
1656 while self.server.wait_for_download:
1657 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001658 l_onoff = 1 # Linger is active.
1659 l_linger = 0 # Seconds to linger for.
1660 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1661 struct.pack('ii', l_onoff, l_linger))
1662
1663 # Close all duplicates of the underlying socket to force the RST.
1664 self.wfile.close()
1665 self.rfile.close()
1666 self.connection.close()
1667
1668 return True
1669
initial.commit94958cf2008-07-26 22:42:52 +00001670 def DefaultResponseHandler(self):
1671 """This is the catch-all response handler for requests that aren't handled
1672 by one of the special handlers above.
1673 Note that we specify the content-length as without it the https connection
1674 is not closed properly (and the browser keeps expecting data)."""
1675
1676 contents = "Default response given for path: " + self.path
1677 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001678 self.send_header('Content-Type', 'text/html')
1679 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001680 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001681 if (self.command != 'HEAD'):
1682 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001683 return True
1684
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001685 def RedirectConnectHandler(self):
1686 """Sends a redirect to the CONNECT request for www.redirect.com. This
1687 response is not specified by the RFC, so the browser should not follow
1688 the redirect."""
1689
1690 if (self.path.find("www.redirect.com") < 0):
1691 return False
1692
1693 dest = "http://www.destination.com/foo.js"
1694
1695 self.send_response(302) # moved temporarily
1696 self.send_header('Location', dest)
1697 self.send_header('Connection', 'close')
1698 self.end_headers()
1699 return True
1700
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001701 def ServerAuthConnectHandler(self):
1702 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1703 response doesn't make sense because the proxy server cannot request
1704 server authentication."""
1705
1706 if (self.path.find("www.server-auth.com") < 0):
1707 return False
1708
1709 challenge = 'Basic realm="WallyWorld"'
1710
1711 self.send_response(401) # unauthorized
1712 self.send_header('WWW-Authenticate', challenge)
1713 self.send_header('Connection', 'close')
1714 self.end_headers()
1715 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001716
1717 def DefaultConnectResponseHandler(self):
1718 """This is the catch-all response handler for CONNECT requests that aren't
1719 handled by one of the special handlers above. Real Web servers respond
1720 with 400 to CONNECT requests."""
1721
1722 contents = "Your client has issued a malformed or illegal request."
1723 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001724 self.send_header('Content-Type', 'text/html')
1725 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001726 self.end_headers()
1727 self.wfile.write(contents)
1728 return True
1729
initial.commit94958cf2008-07-26 22:42:52 +00001730 # called by the redirect handling function when there is no parameter
1731 def sendRedirectHelp(self, redirect_name):
1732 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001733 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001734 self.end_headers()
1735 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1736 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1737 self.wfile.write('</body></html>')
1738
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001739 # called by chunked handling function
1740 def sendChunkHelp(self, chunk):
1741 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1742 self.wfile.write('%X\r\n' % len(chunk))
1743 self.wfile.write(chunk)
1744 self.wfile.write('\r\n')
1745
akalin@chromium.org154bb132010-11-12 02:20:27 +00001746
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001747class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001748 def __init__(self, request, client_address, socket_server):
1749 handlers = [self.OCSPResponse]
1750 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001751 testserver_base.BasePageHandler.__init__(self, request, client_address,
1752 socket_server, [], handlers, [],
1753 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001754
1755 def OCSPResponse(self):
1756 self.send_response(200)
1757 self.send_header('Content-Type', 'application/ocsp-response')
1758 self.send_header('Content-Length', str(len(self.ocsp_response)))
1759 self.end_headers()
1760
1761 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001762
mattm@chromium.org830a3712012-11-07 23:00:07 +00001763
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001764class TCPEchoHandler(SocketServer.BaseRequestHandler):
1765 """The RequestHandler class for TCP echo server.
1766
1767 It is instantiated once per connection to the server, and overrides the
1768 handle() method to implement communication to the client.
1769 """
1770
1771 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001772 """Handles the request from the client and constructs a response."""
1773
1774 data = self.request.recv(65536).strip()
1775 # Verify the "echo request" message received from the client. Send back
1776 # "echo response" message if "echo request" message is valid.
1777 try:
1778 return_data = echo_message.GetEchoResponseData(data)
1779 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001780 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001781 except ValueError:
1782 return
1783
1784 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001785
1786
1787class UDPEchoHandler(SocketServer.BaseRequestHandler):
1788 """The RequestHandler class for UDP echo server.
1789
1790 It is instantiated once per connection to the server, and overrides the
1791 handle() method to implement communication to the client.
1792 """
1793
1794 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001795 """Handles the request from the client and constructs a response."""
1796
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001797 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001798 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001799 # Verify the "echo request" message received from the client. Send back
1800 # "echo response" message if "echo request" message is valid.
1801 try:
1802 return_data = echo_message.GetEchoResponseData(data)
1803 if not return_data:
1804 return
1805 except ValueError:
1806 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001807 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001808
1809
bashi@chromium.org33233532012-09-08 17:37:24 +00001810class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1811 """A request handler that behaves as a proxy server which requires
1812 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1813 """
1814
1815 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1816
1817 def parse_request(self):
1818 """Overrides parse_request to check credential."""
1819
1820 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1821 return False
1822
1823 auth = self.headers.getheader('Proxy-Authorization')
1824 if auth != self._AUTH_CREDENTIAL:
1825 self.send_response(407)
1826 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1827 self.end_headers()
1828 return False
1829
1830 return True
1831
1832 def _start_read_write(self, sock):
1833 sock.setblocking(0)
1834 self.request.setblocking(0)
1835 rlist = [self.request, sock]
1836 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001837 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001838 if errors:
1839 self.send_response(500)
1840 self.end_headers()
1841 return
1842 for s in ready_sockets:
1843 received = s.recv(1024)
1844 if len(received) == 0:
1845 return
1846 if s == self.request:
1847 other = sock
1848 else:
1849 other = self.request
1850 other.send(received)
1851
1852 def _do_common_method(self):
1853 url = urlparse.urlparse(self.path)
1854 port = url.port
1855 if not port:
1856 if url.scheme == 'http':
1857 port = 80
1858 elif url.scheme == 'https':
1859 port = 443
1860 if not url.hostname or not port:
1861 self.send_response(400)
1862 self.end_headers()
1863 return
1864
1865 if len(url.path) == 0:
1866 path = '/'
1867 else:
1868 path = url.path
1869 if len(url.query) > 0:
1870 path = '%s?%s' % (url.path, url.query)
1871
1872 sock = None
1873 try:
1874 sock = socket.create_connection((url.hostname, port))
1875 sock.send('%s %s %s\r\n' % (
1876 self.command, path, self.protocol_version))
1877 for header in self.headers.headers:
1878 header = header.strip()
1879 if (header.lower().startswith('connection') or
1880 header.lower().startswith('proxy')):
1881 continue
1882 sock.send('%s\r\n' % header)
1883 sock.send('\r\n')
1884 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001885 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001886 self.send_response(500)
1887 self.end_headers()
1888 finally:
1889 if sock is not None:
1890 sock.close()
1891
1892 def do_CONNECT(self):
1893 try:
1894 pos = self.path.rfind(':')
1895 host = self.path[:pos]
1896 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001897 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001898 self.send_response(400)
1899 self.end_headers()
1900
1901 try:
1902 sock = socket.create_connection((host, port))
1903 self.send_response(200, 'Connection established')
1904 self.end_headers()
1905 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001906 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001907 self.send_response(500)
1908 self.end_headers()
1909 finally:
1910 sock.close()
1911
1912 def do_GET(self):
1913 self._do_common_method()
1914
1915 def do_HEAD(self):
1916 self._do_common_method()
1917
1918
mattm@chromium.org830a3712012-11-07 23:00:07 +00001919class ServerRunner(testserver_base.TestServerRunner):
1920 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001921
mattm@chromium.org830a3712012-11-07 23:00:07 +00001922 def __init__(self):
1923 super(ServerRunner, self).__init__()
1924 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001925
mattm@chromium.org830a3712012-11-07 23:00:07 +00001926 def __make_data_dir(self):
1927 if self.options.data_dir:
1928 if not os.path.isdir(self.options.data_dir):
1929 raise testserver_base.OptionError('specified data dir not found: ' +
1930 self.options.data_dir + ' exiting...')
1931 my_data_dir = self.options.data_dir
1932 else:
1933 # Create the default path to our data dir, relative to the exe dir.
1934 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1935 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001936
mattm@chromium.org830a3712012-11-07 23:00:07 +00001937 #TODO(ibrar): Must use Find* funtion defined in google\tools
1938 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001939
mattm@chromium.org830a3712012-11-07 23:00:07 +00001940 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001941
mattm@chromium.org830a3712012-11-07 23:00:07 +00001942 def create_server(self, server_data):
1943 port = self.options.port
1944 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001945
mattm@chromium.org830a3712012-11-07 23:00:07 +00001946 if self.options.server_type == SERVER_HTTP:
1947 if self.options.https:
1948 pem_cert_and_key = None
1949 if self.options.cert_and_key_file:
1950 if not os.path.isfile(self.options.cert_and_key_file):
1951 raise testserver_base.OptionError(
1952 'specified server cert file not found: ' +
1953 self.options.cert_and_key_file + ' exiting...')
1954 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001955 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956 # generate a new certificate and run an OCSP server for it.
1957 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001958 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001959 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001960
mattm@chromium.org830a3712012-11-07 23:00:07 +00001961 ocsp_der = None
1962 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001963
mattm@chromium.org830a3712012-11-07 23:00:07 +00001964 if self.options.ocsp == 'ok':
1965 ocsp_state = minica.OCSP_STATE_GOOD
1966 elif self.options.ocsp == 'revoked':
1967 ocsp_state = minica.OCSP_STATE_REVOKED
1968 elif self.options.ocsp == 'invalid':
1969 ocsp_state = minica.OCSP_STATE_INVALID
1970 elif self.options.ocsp == 'unauthorized':
1971 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1972 elif self.options.ocsp == 'unknown':
1973 ocsp_state = minica.OCSP_STATE_UNKNOWN
1974 else:
1975 raise testserver_base.OptionError('unknown OCSP status: ' +
1976 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001977
mattm@chromium.org830a3712012-11-07 23:00:07 +00001978 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1979 subject = "127.0.0.1",
1980 ocsp_url = ("http://%s:%d/ocsp" %
1981 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00001982 ocsp_state = ocsp_state,
1983 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00001984
1985 self.__ocsp_server.ocsp_response = ocsp_der
1986
1987 for ca_cert in self.options.ssl_client_ca:
1988 if not os.path.isfile(ca_cert):
1989 raise testserver_base.OptionError(
1990 'specified trusted client CA file not found: ' + ca_cert +
1991 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00001992
1993 stapled_ocsp_response = None
1994 if self.__ocsp_server and self.options.staple_ocsp_response:
1995 stapled_ocsp_response = self.__ocsp_server.ocsp_response
1996
mattm@chromium.org830a3712012-11-07 23:00:07 +00001997 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1998 self.options.ssl_client_auth,
1999 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002000 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002001 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002002 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002003 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002004 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002005 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002006 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002007 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002008 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002009 self.options.fallback_scsv,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002010 stapled_ocsp_response,
2011 self.options.disable_session_cache)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002012 print 'HTTPS server started on https://%s:%d...' % \
2013 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002014 else:
2015 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002016 print 'HTTP server started on http://%s:%d...' % \
2017 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002018
2019 server.data_dir = self.__make_data_dir()
2020 server.file_root_url = self.options.file_root_url
2021 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002022 elif self.options.server_type == SERVER_WEBSOCKET:
2023 # Launch pywebsocket via WebSocketServer.
2024 logger = logging.getLogger()
2025 logger.addHandler(logging.StreamHandler())
2026 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2027 # is required to work correctly. It should be fixed from pywebsocket side.
2028 os.chdir(self.__make_data_dir())
2029 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002030 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002032 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002033 websocket_options.use_tls = True
2034 websocket_options.private_key = self.options.cert_and_key_file
2035 websocket_options.certificate = self.options.cert_and_key_file
2036 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002037 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002038 websocket_options.tls_client_auth = True
2039 if len(self.options.ssl_client_ca) != 1:
2040 raise testserver_base.OptionError(
2041 'one trusted client CA file should be specified')
2042 if not os.path.isfile(self.options.ssl_client_ca[0]):
2043 raise testserver_base.OptionError(
2044 'specified trusted client CA file not found: ' +
2045 self.options.ssl_client_ca[0] + ' exiting...')
2046 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2047 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002048 print 'WebSocket server started on %s://%s:%d...' % \
2049 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002051 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 elif self.options.server_type == SERVER_TCP_ECHO:
2053 # Used for generating the key (randomly) that encodes the "echo request"
2054 # message.
2055 random.seed()
2056 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002057 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002058 server_data['port'] = server.server_port
2059 elif self.options.server_type == SERVER_UDP_ECHO:
2060 # Used for generating the key (randomly) that encodes the "echo request"
2061 # message.
2062 random.seed()
2063 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002064 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002065 server_data['port'] = server.server_port
2066 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2067 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002068 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002069 server_data['port'] = server.server_port
2070 elif self.options.server_type == SERVER_FTP:
2071 my_data_dir = self.__make_data_dir()
2072
2073 # Instantiate a dummy authorizer for managing 'virtual' users
2074 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2075
2076 # Define a new user having full r/w permissions and a read-only
2077 # anonymous user
2078 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2079
2080 authorizer.add_anonymous(my_data_dir)
2081
2082 # Instantiate FTP handler class
2083 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2084 ftp_handler.authorizer = authorizer
2085
2086 # Define a customized banner (string returned when client connects)
2087 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2088 pyftpdlib.ftpserver.__ver__)
2089
2090 # Instantiate FTP server class and listen to address:port
2091 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2092 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002093 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002094 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 raise testserver_base.OptionError('unknown server type' +
2096 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002097
mattm@chromium.org830a3712012-11-07 23:00:07 +00002098 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002099
mattm@chromium.org830a3712012-11-07 23:00:07 +00002100 def run_server(self):
2101 if self.__ocsp_server:
2102 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002103
mattm@chromium.org830a3712012-11-07 23:00:07 +00002104 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002105
mattm@chromium.org830a3712012-11-07 23:00:07 +00002106 if self.__ocsp_server:
2107 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002108
mattm@chromium.org830a3712012-11-07 23:00:07 +00002109 def add_options(self):
2110 testserver_base.TestServerRunner.add_options(self)
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002111 self.option_parser.add_option('--disable-session-cache',
2112 action='store_true',
2113 dest='disable_session_cache',
2114 help='tells the server to disable the'
2115 'TLS session cache.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002116 self.option_parser.add_option('-f', '--ftp', action='store_const',
2117 const=SERVER_FTP, default=SERVER_HTTP,
2118 dest='server_type',
2119 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002120 self.option_parser.add_option('--tcp-echo', action='store_const',
2121 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2122 dest='server_type',
2123 help='start up a tcp echo server.')
2124 self.option_parser.add_option('--udp-echo', action='store_const',
2125 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2126 dest='server_type',
2127 help='start up a udp echo server.')
2128 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2129 const=SERVER_BASIC_AUTH_PROXY,
2130 default=SERVER_HTTP, dest='server_type',
2131 help='start up a proxy server which requires '
2132 'basic authentication.')
2133 self.option_parser.add_option('--websocket', action='store_const',
2134 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2135 dest='server_type',
2136 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002137 self.option_parser.add_option('--https', action='store_true',
2138 dest='https', help='Specify that https '
2139 'should be used.')
2140 self.option_parser.add_option('--cert-and-key-file',
2141 dest='cert_and_key_file', help='specify the '
2142 'path to the file containing the certificate '
2143 'and private key for the server in PEM '
2144 'format')
2145 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2146 help='The type of OCSP response generated '
2147 'for the automatically generated '
2148 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002149 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2150 default=0, type=int,
2151 help='If non-zero then the generated '
2152 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002153 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2154 default='0', type='int',
2155 help='If nonzero, certain TLS connections '
2156 'will be aborted in order to test version '
2157 'fallback. 1 means all TLS versions will be '
2158 'aborted. 2 means TLS 1.1 or higher will be '
2159 'aborted. 3 means TLS 1.2 or higher will be '
2160 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002161 self.option_parser.add_option('--tls-intolerance-type',
2162 dest='tls_intolerance_type',
2163 default="alert",
2164 help='Controls how the server reacts to a '
2165 'TLS version it is intolerant to. Valid '
2166 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002167 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2168 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002169 default='',
2170 help='Base64 encoded SCT list. If set, '
2171 'server will respond with a '
2172 'signed_certificate_timestamp TLS extension '
2173 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002174 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2175 default=False, const=True,
2176 action='store_const',
2177 help='If given, TLS_FALLBACK_SCSV support '
2178 'will be enabled. This causes the server to '
2179 'reject fallback connections from compatible '
2180 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002181 self.option_parser.add_option('--staple-ocsp-response',
2182 dest='staple_ocsp_response',
2183 default=False, action='store_true',
2184 help='If set, server will staple the OCSP '
2185 'response whenever OCSP is on and the client '
2186 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002187 self.option_parser.add_option('--https-record-resume',
2188 dest='record_resume', const=True,
2189 default=False, action='store_const',
2190 help='Record resumption cache events rather '
2191 'than resuming as normal. Allows the use of '
2192 'the /ssl-session-cache request')
2193 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2194 help='Require SSL client auth on every '
2195 'connection.')
2196 self.option_parser.add_option('--ssl-client-ca', action='append',
2197 default=[], help='Specify that the client '
2198 'certificate request should include the CA '
2199 'named in the subject of the DER-encoded '
2200 'certificate contained in the specified '
2201 'file. This option may appear multiple '
2202 'times, indicating multiple CA names should '
2203 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002204 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2205 default=[], help='Specify that the client '
2206 'certificate request should include the '
2207 'specified certificate_type value. This '
2208 'option may appear multiple times, '
2209 'indicating multiple values should be send '
2210 'in the request. Valid values are '
2211 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2212 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002213 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2214 help='Specify the bulk encryption '
2215 'algorithm(s) that will be accepted by the '
2216 'SSL server. Valid values are "aes256", '
2217 '"aes128", "3des", "rc4". If omitted, all '
2218 'algorithms will be used. This option may '
2219 'appear multiple times, indicating '
2220 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002221 self.option_parser.add_option('--ssl-key-exchange', action='append',
2222 help='Specify the key exchange algorithm(s)'
2223 'that will be accepted by the SSL server. '
2224 'Valid values are "rsa", "dhe_rsa". If '
2225 'omitted, all algorithms will be used. This '
2226 'option may appear multiple times, '
2227 'indicating multiple algorithms should be '
2228 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002229 # TODO(davidben): Add ALPN support to tlslite.
2230 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2231 default=False, const=True,
2232 action='store_const',
2233 help='Enable server support for the NPN '
2234 'extension. The server will advertise '
2235 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002236 self.option_parser.add_option('--file-root-url', default='/files/',
2237 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002238 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2239 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2240 dest='ws_basic_auth',
2241 help='Enable basic-auth for WebSocket')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002242
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002243
initial.commit94958cf2008-07-26 22:42:52 +00002244if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002245 sys.exit(ServerRunner().main())