blob: 77399023f4168504ec35a2395997181de28b68a8 [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,
naskoe7a0d0d2014-09-29 08:53:05 -0700334 self.CrossSiteRedirectHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000335 self.ClientRedirectHandler,
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000336 self.GetSSLSessionCacheHandler,
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +0000337 self.SSLManySmallRecords,
agl@chromium.org04700be2013-03-02 18:40:41 +0000338 self.GetChannelID,
davidben599e7e72014-09-03 16:19:09 -0700339 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000340 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000341 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000342 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000343 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000344 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000345 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000346 self.PostOnlyFileHandler,
347 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000348 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000349 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000350 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000351 head_handlers = [
352 self.FileHandler,
353 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000354
maruel@google.come250a9b2009-03-10 17:39:46 +0000355 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000356 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000357 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000358 'gif': 'image/gif',
359 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000360 'jpg' : 'image/jpeg',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000361 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000362 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000363 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000364 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000365 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000366 }
initial.commit94958cf2008-07-26 22:42:52 +0000367 self._default_mime_type = 'text/html'
368
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000369 testserver_base.BasePageHandler.__init__(self, request, client_address,
370 socket_server, connect_handlers,
371 get_handlers, head_handlers,
372 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000373
initial.commit94958cf2008-07-26 22:42:52 +0000374 def GetMIMETypeFromName(self, file_name):
375 """Returns the mime type for the specified file_name. So far it only looks
376 at the file extension."""
377
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000378 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000379 if len(extension) == 0:
380 # no extension.
381 return self._default_mime_type
382
ericroman@google.comc17ca532009-05-07 03:51:05 +0000383 # extension starts with a dot, so we need to remove it
384 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000385
initial.commit94958cf2008-07-26 22:42:52 +0000386 def NoCacheMaxAgeTimeHandler(self):
387 """This request handler yields a page with the title set to the current
388 system time, and no caching requested."""
389
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000390 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000391 return False
392
393 self.send_response(200)
394 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000395 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000396 self.end_headers()
397
maruel@google.come250a9b2009-03-10 17:39:46 +0000398 self.wfile.write('<html><head><title>%s</title></head></html>' %
399 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000400
401 return True
402
403 def NoCacheTimeHandler(self):
404 """This request handler yields a page with the title set to the current
405 system time, and no caching requested."""
406
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000407 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000408 return False
409
410 self.send_response(200)
411 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000412 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000413 self.end_headers()
414
maruel@google.come250a9b2009-03-10 17:39:46 +0000415 self.wfile.write('<html><head><title>%s</title></head></html>' %
416 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000417
418 return True
419
420 def CacheTimeHandler(self):
421 """This request handler yields a page with the title set to the current
422 system time, and allows caching for one minute."""
423
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000424 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000425 return False
426
427 self.send_response(200)
428 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000429 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000430 self.end_headers()
431
maruel@google.come250a9b2009-03-10 17:39:46 +0000432 self.wfile.write('<html><head><title>%s</title></head></html>' %
433 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000434
435 return True
436
437 def CacheExpiresHandler(self):
438 """This request handler yields a page with the title set to the current
439 system time, and set the page to expire on 1 Jan 2099."""
440
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000441 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000442 return False
443
444 self.send_response(200)
445 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000446 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000447 self.end_headers()
448
maruel@google.come250a9b2009-03-10 17:39:46 +0000449 self.wfile.write('<html><head><title>%s</title></head></html>' %
450 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000451
452 return True
453
454 def CacheProxyRevalidateHandler(self):
455 """This request handler yields a page with the title set to the current
456 system time, and allows caching for 60 seconds"""
457
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000458 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000459 return False
460
461 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000462 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000463 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
464 self.end_headers()
465
maruel@google.come250a9b2009-03-10 17:39:46 +0000466 self.wfile.write('<html><head><title>%s</title></head></html>' %
467 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000468
469 return True
470
471 def CachePrivateHandler(self):
472 """This request handler yields a page with the title set to the current
473 system time, and allows caching for 5 seconds."""
474
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000475 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000476 return False
477
478 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000479 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000480 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000481 self.end_headers()
482
maruel@google.come250a9b2009-03-10 17:39:46 +0000483 self.wfile.write('<html><head><title>%s</title></head></html>' %
484 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000485
486 return True
487
488 def CachePublicHandler(self):
489 """This request handler yields a page with the title set to the current
490 system time, and allows caching for 5 seconds."""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000496 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000497 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000498 self.end_headers()
499
maruel@google.come250a9b2009-03-10 17:39:46 +0000500 self.wfile.write('<html><head><title>%s</title></head></html>' %
501 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000502
503 return True
504
505 def CacheSMaxAgeHandler(self):
506 """This request handler yields a page with the title set to the current
507 system time, and does not allow for caching."""
508
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000509 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000510 return False
511
512 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000513 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000514 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
515 self.end_headers()
516
maruel@google.come250a9b2009-03-10 17:39:46 +0000517 self.wfile.write('<html><head><title>%s</title></head></html>' %
518 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000519
520 return True
521
522 def CacheMustRevalidateHandler(self):
523 """This request handler yields a page with the title set to the current
524 system time, and does not allow caching."""
525
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000526 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000527 return False
528
529 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000530 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000531 self.send_header('Cache-Control', 'must-revalidate')
532 self.end_headers()
533
maruel@google.come250a9b2009-03-10 17:39:46 +0000534 self.wfile.write('<html><head><title>%s</title></head></html>' %
535 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000536
537 return True
538
539 def CacheMustRevalidateMaxAgeHandler(self):
540 """This request handler yields a page with the title set to the current
541 system time, and does not allow caching event though max-age of 60
542 seconds is specified."""
543
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000544 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000545 return False
546
547 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000548 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000549 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
550 self.end_headers()
551
maruel@google.come250a9b2009-03-10 17:39:46 +0000552 self.wfile.write('<html><head><title>%s</title></head></html>' %
553 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000554
555 return True
556
initial.commit94958cf2008-07-26 22:42:52 +0000557 def CacheNoStoreHandler(self):
558 """This request handler yields a page with the title set to the current
559 system time, and does not allow the page to be stored."""
560
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000561 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000562 return False
563
564 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000565 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000566 self.send_header('Cache-Control', 'no-store')
567 self.end_headers()
568
maruel@google.come250a9b2009-03-10 17:39:46 +0000569 self.wfile.write('<html><head><title>%s</title></head></html>' %
570 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000571
572 return True
573
574 def CacheNoStoreMaxAgeHandler(self):
575 """This request handler yields a page with the title set to the current
576 system time, and does not allow the page to be stored even though max-age
577 of 60 seconds is specified."""
578
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000579 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000580 return False
581
582 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000583 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000584 self.send_header('Cache-Control', 'max-age=60, no-store')
585 self.end_headers()
586
maruel@google.come250a9b2009-03-10 17:39:46 +0000587 self.wfile.write('<html><head><title>%s</title></head></html>' %
588 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000589
590 return True
591
592
593 def CacheNoTransformHandler(self):
594 """This request handler yields a page with the title set to the current
595 system time, and does not allow the content to transformed during
596 user-agent caching"""
597
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000598 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000599 return False
600
601 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000602 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000603 self.send_header('Cache-Control', 'no-transform')
604 self.end_headers()
605
maruel@google.come250a9b2009-03-10 17:39:46 +0000606 self.wfile.write('<html><head><title>%s</title></head></html>' %
607 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000608
609 return True
610
611 def EchoHeader(self):
612 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000613
ananta@chromium.org219b2062009-10-23 16:09:41 +0000614 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000615
ananta@chromium.org56812d02011-04-07 17:52:05 +0000616 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000617 """This function echoes back the value of a specific request header while
618 allowing caching for 16 hours."""
619
ananta@chromium.org56812d02011-04-07 17:52:05 +0000620 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000621
622 def EchoHeaderHelper(self, echo_header):
623 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000624
ananta@chromium.org219b2062009-10-23 16:09:41 +0000625 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000626 return False
627
628 query_char = self.path.find('?')
629 if query_char != -1:
630 header_name = self.path[query_char+1:]
631
632 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000633 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000634 if echo_header == '/echoheadercache':
635 self.send_header('Cache-control', 'max-age=60000')
636 else:
637 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000638 # insert a vary header to properly indicate that the cachability of this
639 # request is subject to value of the request header being echoed.
640 if len(header_name) > 0:
641 self.send_header('Vary', header_name)
642 self.end_headers()
643
644 if len(header_name) > 0:
645 self.wfile.write(self.headers.getheader(header_name))
646
647 return True
648
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000649 def ReadRequestBody(self):
650 """This function reads the body of the current HTTP request, handling
651 both plain and chunked transfer encoded requests."""
652
653 if self.headers.getheader('transfer-encoding') != 'chunked':
654 length = int(self.headers.getheader('content-length'))
655 return self.rfile.read(length)
656
657 # Read the request body as chunks.
658 body = ""
659 while True:
660 line = self.rfile.readline()
661 length = int(line, 16)
662 if length == 0:
663 self.rfile.readline()
664 break
665 body += self.rfile.read(length)
666 self.rfile.read(2)
667 return body
668
initial.commit94958cf2008-07-26 22:42:52 +0000669 def EchoHandler(self):
670 """This handler just echoes back the payload of the request, for testing
671 form submission."""
672
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000673 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000674 return False
675
676 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000677 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000678 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000679 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000680 return True
681
682 def EchoTitleHandler(self):
683 """This handler is like Echo, but sets the page title to the request."""
684
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000685 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000686 return False
687
688 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000689 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000690 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000691 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000692 self.wfile.write('<html><head><title>')
693 self.wfile.write(request)
694 self.wfile.write('</title></head></html>')
695 return True
696
697 def EchoAllHandler(self):
698 """This handler yields a (more) human-readable page listing information
699 about the request header & contents."""
700
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000701 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000702 return False
703
704 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000705 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000706 self.end_headers()
707 self.wfile.write('<html><head><style>'
708 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
709 '</style></head><body>'
710 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000711 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000712 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000713
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000714 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000715 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000716 params = cgi.parse_qs(qs, keep_blank_values=1)
717
718 for param in params:
719 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000720
721 self.wfile.write('</pre>')
722
723 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
724
725 self.wfile.write('</body></html>')
726 return True
727
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000728 def EchoMultipartPostHandler(self):
729 """This handler echoes received multipart post data as json format."""
730
731 if not (self._ShouldHandleRequest("/echomultipartpost") or
732 self._ShouldHandleRequest("/searchbyimage")):
733 return False
734
735 content_type, parameters = cgi.parse_header(
736 self.headers.getheader('content-type'))
737 if content_type == 'multipart/form-data':
738 post_multipart = cgi.parse_multipart(self.rfile, parameters)
739 elif content_type == 'application/x-www-form-urlencoded':
740 raise Exception('POST by application/x-www-form-urlencoded is '
741 'not implemented.')
742 else:
743 post_multipart = {}
744
745 # Since the data can be binary, we encode them by base64.
746 post_multipart_base64_encoded = {}
747 for field, values in post_multipart.items():
748 post_multipart_base64_encoded[field] = [base64.b64encode(value)
749 for value in values]
750
751 result = {'POST_multipart' : post_multipart_base64_encoded}
752
753 self.send_response(200)
754 self.send_header("Content-type", "text/plain")
755 self.end_headers()
756 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
757 return True
758
initial.commit94958cf2008-07-26 22:42:52 +0000759 def DownloadHandler(self):
760 """This handler sends a downloadable file with or without reporting
761 the size (6K)."""
762
763 if self.path.startswith("/download-unknown-size"):
764 send_length = False
765 elif self.path.startswith("/download-known-size"):
766 send_length = True
767 else:
768 return False
769
770 #
771 # The test which uses this functionality is attempting to send
772 # small chunks of data to the client. Use a fairly large buffer
773 # so that we'll fill chrome's IO buffer enough to force it to
774 # actually write the data.
775 # See also the comments in the client-side of this test in
776 # download_uitest.cc
777 #
778 size_chunk1 = 35*1024
779 size_chunk2 = 10*1024
780
781 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000782 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000783 self.send_header('Cache-Control', 'max-age=0')
784 if send_length:
785 self.send_header('Content-Length', size_chunk1 + size_chunk2)
786 self.end_headers()
787
788 # First chunk of data:
789 self.wfile.write("*" * size_chunk1)
790 self.wfile.flush()
791
792 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000793 self.server.wait_for_download = True
794 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000795 self.server.handle_request()
796
797 # Second chunk of data:
798 self.wfile.write("*" * size_chunk2)
799 return True
800
801 def DownloadFinishHandler(self):
802 """This handler just tells the server to finish the current download."""
803
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000804 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000805 return False
806
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000807 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000808 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000809 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000810 self.send_header('Cache-Control', 'max-age=0')
811 self.end_headers()
812 return True
813
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000814 def _ReplaceFileData(self, data, query_parameters):
815 """Replaces matching substrings in a file.
816
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000817 If the 'replace_text' URL query parameter is present, it is expected to be
818 of the form old_text:new_text, which indicates that any old_text strings in
819 the file are replaced with new_text. Multiple 'replace_text' parameters may
820 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000821
822 If the parameters are not present, |data| is returned.
823 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000824
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000825 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000826 replace_text_values = query_dict.get('replace_text', [])
827 for replace_text_value in replace_text_values:
828 replace_text_args = replace_text_value.split(':')
829 if len(replace_text_args) != 2:
830 raise ValueError(
831 'replace_text must be of form old_text:new_text. Actual value: %s' %
832 replace_text_value)
833 old_text_b64, new_text_b64 = replace_text_args
834 old_text = base64.urlsafe_b64decode(old_text_b64)
835 new_text = base64.urlsafe_b64decode(new_text_b64)
836 data = data.replace(old_text, new_text)
837 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000838
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000839 def ZipFileHandler(self):
840 """This handler sends the contents of the requested file in compressed form.
841 Can pass in a parameter that specifies that the content length be
842 C - the compressed size (OK),
843 U - the uncompressed size (Non-standard, but handled),
844 S - less than compressed (OK because we keep going),
845 M - larger than compressed but less than uncompressed (an error),
846 L - larger than uncompressed (an error)
847 Example: compressedfiles/Picture_1.doc?C
848 """
849
850 prefix = "/compressedfiles/"
851 if not self.path.startswith(prefix):
852 return False
853
854 # Consume a request body if present.
855 if self.command == 'POST' or self.command == 'PUT' :
856 self.ReadRequestBody()
857
858 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
859
860 if not query in ('C', 'U', 'S', 'M', 'L'):
861 return False
862
863 sub_path = url_path[len(prefix):]
864 entries = sub_path.split('/')
865 file_path = os.path.join(self.server.data_dir, *entries)
866 if os.path.isdir(file_path):
867 file_path = os.path.join(file_path, 'index.html')
868
869 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000870 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000871 self.send_error(404)
872 return True
873
874 f = open(file_path, "rb")
875 data = f.read()
876 uncompressed_len = len(data)
877 f.close()
878
879 # Compress the data.
880 data = zlib.compress(data)
881 compressed_len = len(data)
882
883 content_length = compressed_len
884 if query == 'U':
885 content_length = uncompressed_len
886 elif query == 'S':
887 content_length = compressed_len / 2
888 elif query == 'M':
889 content_length = (compressed_len + uncompressed_len) / 2
890 elif query == 'L':
891 content_length = compressed_len + uncompressed_len
892
893 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000894 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000895 self.send_header('Content-encoding', 'deflate')
896 self.send_header('Connection', 'close')
897 self.send_header('Content-Length', content_length)
898 self.send_header('ETag', '\'' + file_path + '\'')
899 self.end_headers()
900
901 self.wfile.write(data)
902
903 return True
904
initial.commit94958cf2008-07-26 22:42:52 +0000905 def FileHandler(self):
906 """This handler sends the contents of the requested file. Wow, it's like
907 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000908
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000909 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000910 if not self.path.startswith(prefix):
911 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000912 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000913
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000914 def PostOnlyFileHandler(self):
915 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000916
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000917 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000918 if not self.path.startswith(prefix):
919 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000920 return self._FileHandlerHelper(prefix)
921
922 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000923 request_body = ''
924 if self.command == 'POST' or self.command == 'PUT':
925 # Consume a request body if present.
926 request_body = self.ReadRequestBody()
927
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000928 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000929 query_dict = cgi.parse_qs(query)
930
931 expected_body = query_dict.get('expected_body', [])
932 if expected_body and request_body not in expected_body:
933 self.send_response(404)
934 self.end_headers()
935 self.wfile.write('')
936 return True
937
938 expected_headers = query_dict.get('expected_headers', [])
939 for expected_header in expected_headers:
940 header_name, expected_value = expected_header.split(':')
941 if self.headers.getheader(header_name) != expected_value:
942 self.send_response(404)
943 self.end_headers()
944 self.wfile.write('')
945 return True
946
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000947 sub_path = url_path[len(prefix):]
948 entries = sub_path.split('/')
949 file_path = os.path.join(self.server.data_dir, *entries)
950 if os.path.isdir(file_path):
951 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000952
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000953 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000954 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000955 self.send_error(404)
956 return True
957
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000958 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000959 data = f.read()
960 f.close()
961
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000962 data = self._ReplaceFileData(data, query)
963
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000964 old_protocol_version = self.protocol_version
965
initial.commit94958cf2008-07-26 22:42:52 +0000966 # If file.mock-http-headers exists, it contains the headers we
967 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000968 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000969 if os.path.isfile(headers_path):
970 f = open(headers_path, "r")
971
972 # "HTTP/1.1 200 OK"
973 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000974 http_major, http_minor, status_code = re.findall(
975 'HTTP/(\d+).(\d+) (\d+)', response)[0]
976 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000977 self.send_response(int(status_code))
978
979 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000980 header_values = re.findall('(\S+):\s*(.*)', line)
981 if len(header_values) > 0:
982 # "name: value"
983 name, value = header_values[0]
984 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000985 f.close()
986 else:
987 # Could be more generic once we support mime-type sniffing, but for
988 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000989
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000990 range_header = self.headers.get('Range')
991 if range_header and range_header.startswith('bytes='):
992 # Note this doesn't handle all valid byte range_header values (i.e.
993 # left open ended ones), just enough for what we needed so far.
994 range_header = range_header[6:].split('-')
995 start = int(range_header[0])
996 if range_header[1]:
997 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000998 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +0000999 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001000
1001 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001002 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1003 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001004 self.send_header('Content-Range', content_range)
1005 data = data[start: end + 1]
1006 else:
1007 self.send_response(200)
1008
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001009 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001010 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001011 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001012 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001013 self.end_headers()
1014
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001015 if (self.command != 'HEAD'):
1016 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001017
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001018 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001019 return True
1020
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001021 def SetCookieHandler(self):
1022 """This handler just sets a cookie, for testing cookie handling."""
1023
1024 if not self._ShouldHandleRequest("/set-cookie"):
1025 return False
1026
1027 query_char = self.path.find('?')
1028 if query_char != -1:
1029 cookie_values = self.path[query_char + 1:].split('&')
1030 else:
1031 cookie_values = ("",)
1032 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001033 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001034 for cookie_value in cookie_values:
1035 self.send_header('Set-Cookie', '%s' % cookie_value)
1036 self.end_headers()
1037 for cookie_value in cookie_values:
1038 self.wfile.write('%s' % cookie_value)
1039 return True
1040
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001041 def SetManyCookiesHandler(self):
1042 """This handler just sets a given number of cookies, for testing handling
1043 of large numbers of cookies."""
1044
1045 if not self._ShouldHandleRequest("/set-many-cookies"):
1046 return False
1047
1048 query_char = self.path.find('?')
1049 if query_char != -1:
1050 num_cookies = int(self.path[query_char + 1:])
1051 else:
1052 num_cookies = 0
1053 self.send_response(200)
1054 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001055 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001056 self.send_header('Set-Cookie', 'a=')
1057 self.end_headers()
1058 self.wfile.write('%d cookies were sent' % num_cookies)
1059 return True
1060
mattm@chromium.org983fc462012-06-30 00:52:08 +00001061 def ExpectAndSetCookieHandler(self):
1062 """Expects some cookies to be sent, and if they are, sets more cookies.
1063
1064 The expect parameter specifies a required cookie. May be specified multiple
1065 times.
1066 The set parameter specifies a cookie to set if all required cookies are
1067 preset. May be specified multiple times.
1068 The data parameter specifies the response body data to be returned."""
1069
1070 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1071 return False
1072
1073 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1074 query_dict = cgi.parse_qs(query)
1075 cookies = set()
1076 if 'Cookie' in self.headers:
1077 cookie_header = self.headers.getheader('Cookie')
1078 cookies.update([s.strip() for s in cookie_header.split(';')])
1079 got_all_expected_cookies = True
1080 for expected_cookie in query_dict.get('expect', []):
1081 if expected_cookie not in cookies:
1082 got_all_expected_cookies = False
1083 self.send_response(200)
1084 self.send_header('Content-Type', 'text/html')
1085 if got_all_expected_cookies:
1086 for cookie_value in query_dict.get('set', []):
1087 self.send_header('Set-Cookie', '%s' % cookie_value)
1088 self.end_headers()
1089 for data_value in query_dict.get('data', []):
1090 self.wfile.write(data_value)
1091 return True
1092
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001093 def SetHeaderHandler(self):
1094 """This handler sets a response header. Parameters are in the
1095 key%3A%20value&key2%3A%20value2 format."""
1096
1097 if not self._ShouldHandleRequest("/set-header"):
1098 return False
1099
1100 query_char = self.path.find('?')
1101 if query_char != -1:
1102 headers_values = self.path[query_char + 1:].split('&')
1103 else:
1104 headers_values = ("",)
1105 self.send_response(200)
1106 self.send_header('Content-Type', 'text/html')
1107 for header_value in headers_values:
1108 header_value = urllib.unquote(header_value)
1109 (key, value) = header_value.split(': ', 1)
1110 self.send_header(key, value)
1111 self.end_headers()
1112 for header_value in headers_values:
1113 self.wfile.write('%s' % header_value)
1114 return True
1115
initial.commit94958cf2008-07-26 22:42:52 +00001116 def AuthBasicHandler(self):
1117 """This handler tests 'Basic' authentication. It just sends a page with
1118 title 'user/pass' if you succeed."""
1119
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001120 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001121 return False
1122
1123 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001124 expected_password = 'secret'
1125 realm = 'testrealm'
1126 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001127
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001128 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1129 query_params = cgi.parse_qs(query, True)
1130 if 'set-cookie-if-challenged' in query_params:
1131 set_cookie_if_challenged = True
1132 if 'password' in query_params:
1133 expected_password = query_params['password'][0]
1134 if 'realm' in query_params:
1135 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001136
initial.commit94958cf2008-07-26 22:42:52 +00001137 auth = self.headers.getheader('authorization')
1138 try:
1139 if not auth:
1140 raise Exception('no auth')
1141 b64str = re.findall(r'Basic (\S+)', auth)[0]
1142 userpass = base64.b64decode(b64str)
1143 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001144 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001145 raise Exception('wrong password')
1146 except Exception, e:
1147 # Authentication failed.
1148 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001149 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001150 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001151 if set_cookie_if_challenged:
1152 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001153 self.end_headers()
1154 self.wfile.write('<html><head>')
1155 self.wfile.write('<title>Denied: %s</title>' % e)
1156 self.wfile.write('</head><body>')
1157 self.wfile.write('auth=%s<p>' % auth)
1158 self.wfile.write('b64str=%s<p>' % b64str)
1159 self.wfile.write('username: %s<p>' % username)
1160 self.wfile.write('userpass: %s<p>' % userpass)
1161 self.wfile.write('password: %s<p>' % password)
1162 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1163 self.wfile.write('</body></html>')
1164 return True
1165
1166 # Authentication successful. (Return a cachable response to allow for
1167 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001168 old_protocol_version = self.protocol_version
1169 self.protocol_version = "HTTP/1.1"
1170
initial.commit94958cf2008-07-26 22:42:52 +00001171 if_none_match = self.headers.getheader('if-none-match')
1172 if if_none_match == "abc":
1173 self.send_response(304)
1174 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001175 elif url_path.endswith(".gif"):
1176 # Using chrome/test/data/google/logo.gif as the test image
1177 test_image_path = ['google', 'logo.gif']
1178 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1179 if not os.path.isfile(gif_path):
1180 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001181 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001182 return True
1183
1184 f = open(gif_path, "rb")
1185 data = f.read()
1186 f.close()
1187
1188 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001189 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001190 self.send_header('Cache-control', 'max-age=60000')
1191 self.send_header('Etag', 'abc')
1192 self.end_headers()
1193 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001194 else:
1195 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001196 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001197 self.send_header('Cache-control', 'max-age=60000')
1198 self.send_header('Etag', 'abc')
1199 self.end_headers()
1200 self.wfile.write('<html><head>')
1201 self.wfile.write('<title>%s/%s</title>' % (username, password))
1202 self.wfile.write('</head><body>')
1203 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001204 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001205 self.wfile.write('</body></html>')
1206
rvargas@google.com54453b72011-05-19 01:11:11 +00001207 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001208 return True
1209
tonyg@chromium.org75054202010-03-31 22:06:10 +00001210 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001211 """Returns a nonce that's stable per request path for the server's lifetime.
1212 This is a fake implementation. A real implementation would only use a given
1213 nonce a single time (hence the name n-once). However, for the purposes of
1214 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001215
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001216 Args:
1217 force_reset: Iff set, the nonce will be changed. Useful for testing the
1218 "stale" response.
1219 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001220
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001221 if force_reset or not self.server.nonce_time:
1222 self.server.nonce_time = time.time()
1223 return hashlib.md5('privatekey%s%d' %
1224 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001225
1226 def AuthDigestHandler(self):
1227 """This handler tests 'Digest' authentication.
1228
1229 It just sends a page with title 'user/pass' if you succeed.
1230
1231 A stale response is sent iff "stale" is present in the request path.
1232 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001233
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001234 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001235 return False
1236
tonyg@chromium.org75054202010-03-31 22:06:10 +00001237 stale = 'stale' in self.path
1238 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001239 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001240 password = 'secret'
1241 realm = 'testrealm'
1242
1243 auth = self.headers.getheader('authorization')
1244 pairs = {}
1245 try:
1246 if not auth:
1247 raise Exception('no auth')
1248 if not auth.startswith('Digest'):
1249 raise Exception('not digest')
1250 # Pull out all the name="value" pairs as a dictionary.
1251 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1252
1253 # Make sure it's all valid.
1254 if pairs['nonce'] != nonce:
1255 raise Exception('wrong nonce')
1256 if pairs['opaque'] != opaque:
1257 raise Exception('wrong opaque')
1258
1259 # Check the 'response' value and make sure it matches our magic hash.
1260 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001261 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001262 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001263 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001264 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001265 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001266 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1267 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001268 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001269
1270 if pairs['response'] != response:
1271 raise Exception('wrong password')
1272 except Exception, e:
1273 # Authentication failed.
1274 self.send_response(401)
1275 hdr = ('Digest '
1276 'realm="%s", '
1277 'domain="/", '
1278 'qop="auth", '
1279 'algorithm=MD5, '
1280 'nonce="%s", '
1281 'opaque="%s"') % (realm, nonce, opaque)
1282 if stale:
1283 hdr += ', stale="TRUE"'
1284 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001285 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001286 self.end_headers()
1287 self.wfile.write('<html><head>')
1288 self.wfile.write('<title>Denied: %s</title>' % e)
1289 self.wfile.write('</head><body>')
1290 self.wfile.write('auth=%s<p>' % auth)
1291 self.wfile.write('pairs=%s<p>' % pairs)
1292 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1293 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1294 self.wfile.write('</body></html>')
1295 return True
1296
1297 # Authentication successful.
1298 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001299 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001300 self.end_headers()
1301 self.wfile.write('<html><head>')
1302 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1303 self.wfile.write('</head><body>')
1304 self.wfile.write('auth=%s<p>' % auth)
1305 self.wfile.write('pairs=%s<p>' % pairs)
1306 self.wfile.write('</body></html>')
1307
1308 return True
1309
1310 def SlowServerHandler(self):
1311 """Wait for the user suggested time before responding. The syntax is
1312 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001313
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001314 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001315 return False
1316 query_char = self.path.find('?')
1317 wait_sec = 1.0
1318 if query_char >= 0:
1319 try:
1320 wait_sec = int(self.path[query_char + 1:])
1321 except ValueError:
1322 pass
1323 time.sleep(wait_sec)
1324 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001325 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001326 self.end_headers()
1327 self.wfile.write("waited %d seconds" % wait_sec)
1328 return True
1329
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001330 def ChunkedServerHandler(self):
1331 """Send chunked response. Allows to specify chunks parameters:
1332 - waitBeforeHeaders - ms to wait before sending headers
1333 - waitBetweenChunks - ms to wait between chunks
1334 - chunkSize - size of each chunk in bytes
1335 - chunksNumber - number of chunks
1336 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1337 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001338
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001339 if not self._ShouldHandleRequest("/chunked"):
1340 return False
1341 query_char = self.path.find('?')
1342 chunkedSettings = {'waitBeforeHeaders' : 0,
1343 'waitBetweenChunks' : 0,
1344 'chunkSize' : 5,
1345 'chunksNumber' : 5}
1346 if query_char >= 0:
1347 params = self.path[query_char + 1:].split('&')
1348 for param in params:
1349 keyValue = param.split('=')
1350 if len(keyValue) == 2:
1351 try:
1352 chunkedSettings[keyValue[0]] = int(keyValue[1])
1353 except ValueError:
1354 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001355 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001356 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1357 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001358 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001359 self.send_header('Connection', 'close')
1360 self.send_header('Transfer-Encoding', 'chunked')
1361 self.end_headers()
1362 # Chunked encoding: sending all chunks, then final zero-length chunk and
1363 # then final CRLF.
1364 for i in range(0, chunkedSettings['chunksNumber']):
1365 if i > 0:
1366 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1367 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001368 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001369 self.sendChunkHelp('')
1370 return True
1371
initial.commit94958cf2008-07-26 22:42:52 +00001372 def ContentTypeHandler(self):
1373 """Returns a string of html with the given content type. E.g.,
1374 /contenttype?text/css returns an html file with the Content-Type
1375 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001376
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001377 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001378 return False
1379 query_char = self.path.find('?')
1380 content_type = self.path[query_char + 1:].strip()
1381 if not content_type:
1382 content_type = 'text/html'
1383 self.send_response(200)
1384 self.send_header('Content-Type', content_type)
1385 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001386 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001387 return True
1388
creis@google.com2f4f6a42011-03-25 19:44:19 +00001389 def NoContentHandler(self):
1390 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001391
creis@google.com2f4f6a42011-03-25 19:44:19 +00001392 if not self._ShouldHandleRequest("/nocontent"):
1393 return False
1394 self.send_response(204)
1395 self.end_headers()
1396 return True
1397
initial.commit94958cf2008-07-26 22:42:52 +00001398 def ServerRedirectHandler(self):
1399 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001400 '/server-redirect?http://foo.bar/asdf' to redirect to
1401 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001402
1403 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001404 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001405 return False
1406
1407 query_char = self.path.find('?')
1408 if query_char < 0 or len(self.path) <= query_char + 1:
1409 self.sendRedirectHelp(test_name)
1410 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001411 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001412
1413 self.send_response(301) # moved permanently
1414 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001415 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001416 self.end_headers()
1417 self.wfile.write('<html><head>')
1418 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1419
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001420 return True
initial.commit94958cf2008-07-26 22:42:52 +00001421
naskoe7a0d0d2014-09-29 08:53:05 -07001422 def CrossSiteRedirectHandler(self):
1423 """Sends a server redirect to the given site. The syntax is
1424 '/cross-site/hostname/...' to redirect to //hostname/...
1425 It is used to navigate between different Sites, causing
1426 cross-site/cross-process navigations in the browser."""
1427
1428 test_name = "/cross-site"
1429 if not self._ShouldHandleRequest(test_name):
1430 print "CSRH: not handling request for " + test_name
1431 return False
1432
1433 params = urllib.unquote(self.path[(len(test_name) + 1):])
1434 slash = params.find('/')
1435 if slash < 0:
1436 self.sendRedirectHelp(test_name)
1437 return True
1438
1439 host = params[:slash]
1440 path = params[(slash+1):]
1441 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1442
1443 self.send_response(301) # moved permanently
1444 self.send_header('Location', dest)
1445 self.send_header('Content-Type', 'text/html')
1446 self.end_headers()
1447 self.wfile.write('<html><head>')
1448 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1449
1450 return True
1451
initial.commit94958cf2008-07-26 22:42:52 +00001452 def ClientRedirectHandler(self):
1453 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001454 '/client-redirect?http://foo.bar/asdf' to redirect to
1455 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001456
1457 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001458 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001459 return False
1460
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001461 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001462 if query_char < 0 or len(self.path) <= query_char + 1:
1463 self.sendRedirectHelp(test_name)
1464 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001465 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001466
1467 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001468 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001469 self.end_headers()
1470 self.wfile.write('<html><head>')
1471 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1472 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1473
1474 return True
1475
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001476 def GetSSLSessionCacheHandler(self):
1477 """Send a reply containing a log of the session cache operations."""
1478
1479 if not self._ShouldHandleRequest('/ssl-session-cache'):
1480 return False
1481
1482 self.send_response(200)
1483 self.send_header('Content-Type', 'text/plain')
1484 self.end_headers()
1485 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001486 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001487 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001488 self.wfile.write('Pass --https-record-resume in order to use' +
1489 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001490 return True
1491
1492 for (action, sessionID) in log:
1493 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001494 return True
1495
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001496 def SSLManySmallRecords(self):
1497 """Sends a reply consisting of a variety of small writes. These will be
1498 translated into a series of small SSL records when used over an HTTPS
1499 server."""
1500
1501 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1502 return False
1503
1504 self.send_response(200)
1505 self.send_header('Content-Type', 'text/plain')
1506 self.end_headers()
1507
1508 # Write ~26K of data, in 1350 byte chunks
1509 for i in xrange(20):
1510 self.wfile.write('*' * 1350)
1511 self.wfile.flush()
1512 return True
1513
agl@chromium.org04700be2013-03-02 18:40:41 +00001514 def GetChannelID(self):
1515 """Send a reply containing the hashed ChannelID that the client provided."""
1516
1517 if not self._ShouldHandleRequest('/channel-id'):
1518 return False
1519
1520 self.send_response(200)
1521 self.send_header('Content-Type', 'text/plain')
1522 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001523 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001524 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1525 return True
1526
davidben599e7e72014-09-03 16:19:09 -07001527 def ClientCipherListHandler(self):
1528 """Send a reply containing the cipher suite list that the client
1529 provided. Each cipher suite value is serialized in decimal, followed by a
1530 newline."""
1531
1532 if not self._ShouldHandleRequest('/client-cipher-list'):
1533 return False
1534
1535 self.send_response(200)
1536 self.send_header('Content-Type', 'text/plain')
1537 self.end_headers()
1538
1539 for cipher_suite in self.server.tlsConnection.clientHello.cipher_suites:
1540 self.wfile.write(str(cipher_suite))
1541 self.wfile.write('\n')
1542 return True
1543
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001544 def CloseSocketHandler(self):
1545 """Closes the socket without sending anything."""
1546
1547 if not self._ShouldHandleRequest('/close-socket'):
1548 return False
1549
1550 self.wfile.close()
1551 return True
1552
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001553 def RangeResetHandler(self):
1554 """Send data broken up by connection resets every N (default 4K) bytes.
1555 Support range requests. If the data requested doesn't straddle a reset
1556 boundary, it will all be sent. Used for testing resuming downloads."""
1557
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001558 def DataForRange(start, end):
1559 """Data to be provided for a particular range of bytes."""
1560 # Offset and scale to avoid too obvious (and hence potentially
1561 # collidable) data.
1562 return ''.join([chr(y % 256)
1563 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1564
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001565 if not self._ShouldHandleRequest('/rangereset'):
1566 return False
1567
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001568 # HTTP/1.1 is required for ETag and range support.
1569 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001570 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1571
1572 # Defaults
1573 size = 8000
1574 # Note that the rst is sent just before sending the rst_boundary byte.
1575 rst_boundary = 4000
1576 respond_to_range = True
1577 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001578 rst_limit = -1
1579 token = 'DEFAULT'
1580 fail_precondition = 0
1581 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001582
1583 # Parse the query
1584 qdict = urlparse.parse_qs(query, True)
1585 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001586 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001587 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001588 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001589 if 'token' in qdict:
1590 # Identifying token for stateful tests.
1591 token = qdict['token'][0]
1592 if 'rst_limit' in qdict:
1593 # Max number of rsts for a given token.
1594 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001595 if 'bounce_range' in qdict:
1596 respond_to_range = False
1597 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001598 # Note that hold_for_signal will not work with null range requests;
1599 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001600 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001601 if 'no_verifiers' in qdict:
1602 send_verifiers = False
1603 if 'fail_precondition' in qdict:
1604 fail_precondition = int(qdict['fail_precondition'][0])
1605
1606 # Record already set information, or set it.
1607 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1608 if rst_limit != 0:
1609 TestPageHandler.rst_limits[token] -= 1
1610 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1611 token, fail_precondition)
1612 if fail_precondition != 0:
1613 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001614
1615 first_byte = 0
1616 last_byte = size - 1
1617
1618 # Does that define what we want to return, or do we need to apply
1619 # a range?
1620 range_response = False
1621 range_header = self.headers.getheader('range')
1622 if range_header and respond_to_range:
1623 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1624 if mo.group(1):
1625 first_byte = int(mo.group(1))
1626 if mo.group(2):
1627 last_byte = int(mo.group(2))
1628 if last_byte > size - 1:
1629 last_byte = size - 1
1630 range_response = True
1631 if last_byte < first_byte:
1632 return False
1633
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001634 if (fail_precondition and
1635 (self.headers.getheader('If-Modified-Since') or
1636 self.headers.getheader('If-Match'))):
1637 self.send_response(412)
1638 self.end_headers()
1639 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001640
1641 if range_response:
1642 self.send_response(206)
1643 self.send_header('Content-Range',
1644 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1645 else:
1646 self.send_response(200)
1647 self.send_header('Content-Type', 'application/octet-stream')
1648 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001649 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001650 # If fail_precondition is non-zero, then the ETag for each request will be
1651 # different.
1652 etag = "%s%d" % (token, fail_precondition)
1653 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001654 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001655 self.end_headers()
1656
1657 if hold_for_signal:
1658 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1659 # a single byte, the self.server.handle_request() below hangs
1660 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001661 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001662 first_byte = first_byte + 1
1663 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001664 self.server.wait_for_download = True
1665 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001666 self.server.handle_request()
1667
1668 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001669 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001670 # No RST has been requested in this range, so we don't need to
1671 # do anything fancy; just write the data and let the python
1672 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001673 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001674 self.wfile.flush()
1675 return True
1676
1677 # We're resetting the connection part way in; go to the RST
1678 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001679 # Because socket semantics do not guarantee that all the data will be
1680 # sent when using the linger semantics to hard close a socket,
1681 # we send the data and then wait for our peer to release us
1682 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001683 data = DataForRange(first_byte, possible_rst)
1684 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001685 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001686 self.server.wait_for_download = True
1687 while self.server.wait_for_download:
1688 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001689 l_onoff = 1 # Linger is active.
1690 l_linger = 0 # Seconds to linger for.
1691 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1692 struct.pack('ii', l_onoff, l_linger))
1693
1694 # Close all duplicates of the underlying socket to force the RST.
1695 self.wfile.close()
1696 self.rfile.close()
1697 self.connection.close()
1698
1699 return True
1700
initial.commit94958cf2008-07-26 22:42:52 +00001701 def DefaultResponseHandler(self):
1702 """This is the catch-all response handler for requests that aren't handled
1703 by one of the special handlers above.
1704 Note that we specify the content-length as without it the https connection
1705 is not closed properly (and the browser keeps expecting data)."""
1706
1707 contents = "Default response given for path: " + self.path
1708 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001709 self.send_header('Content-Type', 'text/html')
1710 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001711 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001712 if (self.command != 'HEAD'):
1713 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001714 return True
1715
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001716 def RedirectConnectHandler(self):
1717 """Sends a redirect to the CONNECT request for www.redirect.com. This
1718 response is not specified by the RFC, so the browser should not follow
1719 the redirect."""
1720
1721 if (self.path.find("www.redirect.com") < 0):
1722 return False
1723
1724 dest = "http://www.destination.com/foo.js"
1725
1726 self.send_response(302) # moved temporarily
1727 self.send_header('Location', dest)
1728 self.send_header('Connection', 'close')
1729 self.end_headers()
1730 return True
1731
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001732 def ServerAuthConnectHandler(self):
1733 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1734 response doesn't make sense because the proxy server cannot request
1735 server authentication."""
1736
1737 if (self.path.find("www.server-auth.com") < 0):
1738 return False
1739
1740 challenge = 'Basic realm="WallyWorld"'
1741
1742 self.send_response(401) # unauthorized
1743 self.send_header('WWW-Authenticate', challenge)
1744 self.send_header('Connection', 'close')
1745 self.end_headers()
1746 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001747
1748 def DefaultConnectResponseHandler(self):
1749 """This is the catch-all response handler for CONNECT requests that aren't
1750 handled by one of the special handlers above. Real Web servers respond
1751 with 400 to CONNECT requests."""
1752
1753 contents = "Your client has issued a malformed or illegal request."
1754 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001755 self.send_header('Content-Type', 'text/html')
1756 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001757 self.end_headers()
1758 self.wfile.write(contents)
1759 return True
1760
initial.commit94958cf2008-07-26 22:42:52 +00001761 # called by the redirect handling function when there is no parameter
1762 def sendRedirectHelp(self, redirect_name):
1763 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001764 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001765 self.end_headers()
1766 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1767 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1768 self.wfile.write('</body></html>')
1769
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001770 # called by chunked handling function
1771 def sendChunkHelp(self, chunk):
1772 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1773 self.wfile.write('%X\r\n' % len(chunk))
1774 self.wfile.write(chunk)
1775 self.wfile.write('\r\n')
1776
akalin@chromium.org154bb132010-11-12 02:20:27 +00001777
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001778class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001779 def __init__(self, request, client_address, socket_server):
1780 handlers = [self.OCSPResponse]
1781 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001782 testserver_base.BasePageHandler.__init__(self, request, client_address,
1783 socket_server, [], handlers, [],
1784 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001785
1786 def OCSPResponse(self):
1787 self.send_response(200)
1788 self.send_header('Content-Type', 'application/ocsp-response')
1789 self.send_header('Content-Length', str(len(self.ocsp_response)))
1790 self.end_headers()
1791
1792 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001793
mattm@chromium.org830a3712012-11-07 23:00:07 +00001794
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001795class TCPEchoHandler(SocketServer.BaseRequestHandler):
1796 """The RequestHandler class for TCP echo server.
1797
1798 It is instantiated once per connection to the server, and overrides the
1799 handle() method to implement communication to the client.
1800 """
1801
1802 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001803 """Handles the request from the client and constructs a response."""
1804
1805 data = self.request.recv(65536).strip()
1806 # Verify the "echo request" message received from the client. Send back
1807 # "echo response" message if "echo request" message is valid.
1808 try:
1809 return_data = echo_message.GetEchoResponseData(data)
1810 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001811 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001812 except ValueError:
1813 return
1814
1815 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001816
1817
1818class UDPEchoHandler(SocketServer.BaseRequestHandler):
1819 """The RequestHandler class for UDP echo server.
1820
1821 It is instantiated once per connection to the server, and overrides the
1822 handle() method to implement communication to the client.
1823 """
1824
1825 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001826 """Handles the request from the client and constructs a response."""
1827
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001828 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001829 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001830 # Verify the "echo request" message received from the client. Send back
1831 # "echo response" message if "echo request" message is valid.
1832 try:
1833 return_data = echo_message.GetEchoResponseData(data)
1834 if not return_data:
1835 return
1836 except ValueError:
1837 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001838 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001839
1840
bashi@chromium.org33233532012-09-08 17:37:24 +00001841class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1842 """A request handler that behaves as a proxy server which requires
1843 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1844 """
1845
1846 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1847
1848 def parse_request(self):
1849 """Overrides parse_request to check credential."""
1850
1851 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1852 return False
1853
1854 auth = self.headers.getheader('Proxy-Authorization')
1855 if auth != self._AUTH_CREDENTIAL:
1856 self.send_response(407)
1857 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1858 self.end_headers()
1859 return False
1860
1861 return True
1862
1863 def _start_read_write(self, sock):
1864 sock.setblocking(0)
1865 self.request.setblocking(0)
1866 rlist = [self.request, sock]
1867 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001868 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001869 if errors:
1870 self.send_response(500)
1871 self.end_headers()
1872 return
1873 for s in ready_sockets:
1874 received = s.recv(1024)
1875 if len(received) == 0:
1876 return
1877 if s == self.request:
1878 other = sock
1879 else:
1880 other = self.request
1881 other.send(received)
1882
1883 def _do_common_method(self):
1884 url = urlparse.urlparse(self.path)
1885 port = url.port
1886 if not port:
1887 if url.scheme == 'http':
1888 port = 80
1889 elif url.scheme == 'https':
1890 port = 443
1891 if not url.hostname or not port:
1892 self.send_response(400)
1893 self.end_headers()
1894 return
1895
1896 if len(url.path) == 0:
1897 path = '/'
1898 else:
1899 path = url.path
1900 if len(url.query) > 0:
1901 path = '%s?%s' % (url.path, url.query)
1902
1903 sock = None
1904 try:
1905 sock = socket.create_connection((url.hostname, port))
1906 sock.send('%s %s %s\r\n' % (
1907 self.command, path, self.protocol_version))
1908 for header in self.headers.headers:
1909 header = header.strip()
1910 if (header.lower().startswith('connection') or
1911 header.lower().startswith('proxy')):
1912 continue
1913 sock.send('%s\r\n' % header)
1914 sock.send('\r\n')
1915 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001916 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001917 self.send_response(500)
1918 self.end_headers()
1919 finally:
1920 if sock is not None:
1921 sock.close()
1922
1923 def do_CONNECT(self):
1924 try:
1925 pos = self.path.rfind(':')
1926 host = self.path[:pos]
1927 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001928 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001929 self.send_response(400)
1930 self.end_headers()
1931
1932 try:
1933 sock = socket.create_connection((host, port))
1934 self.send_response(200, 'Connection established')
1935 self.end_headers()
1936 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001937 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001938 self.send_response(500)
1939 self.end_headers()
1940 finally:
1941 sock.close()
1942
1943 def do_GET(self):
1944 self._do_common_method()
1945
1946 def do_HEAD(self):
1947 self._do_common_method()
1948
1949
mattm@chromium.org830a3712012-11-07 23:00:07 +00001950class ServerRunner(testserver_base.TestServerRunner):
1951 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001952
mattm@chromium.org830a3712012-11-07 23:00:07 +00001953 def __init__(self):
1954 super(ServerRunner, self).__init__()
1955 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001956
mattm@chromium.org830a3712012-11-07 23:00:07 +00001957 def __make_data_dir(self):
1958 if self.options.data_dir:
1959 if not os.path.isdir(self.options.data_dir):
1960 raise testserver_base.OptionError('specified data dir not found: ' +
1961 self.options.data_dir + ' exiting...')
1962 my_data_dir = self.options.data_dir
1963 else:
1964 # Create the default path to our data dir, relative to the exe dir.
1965 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1966 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001967
mattm@chromium.org830a3712012-11-07 23:00:07 +00001968 #TODO(ibrar): Must use Find* funtion defined in google\tools
1969 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001970
mattm@chromium.org830a3712012-11-07 23:00:07 +00001971 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001972
mattm@chromium.org830a3712012-11-07 23:00:07 +00001973 def create_server(self, server_data):
1974 port = self.options.port
1975 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001976
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 if self.options.server_type == SERVER_HTTP:
1978 if self.options.https:
1979 pem_cert_and_key = None
1980 if self.options.cert_and_key_file:
1981 if not os.path.isfile(self.options.cert_and_key_file):
1982 raise testserver_base.OptionError(
1983 'specified server cert file not found: ' +
1984 self.options.cert_and_key_file + ' exiting...')
1985 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001986 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001987 # generate a new certificate and run an OCSP server for it.
1988 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001989 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001990 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001991
mattm@chromium.org830a3712012-11-07 23:00:07 +00001992 ocsp_der = None
1993 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001994
mattm@chromium.org830a3712012-11-07 23:00:07 +00001995 if self.options.ocsp == 'ok':
1996 ocsp_state = minica.OCSP_STATE_GOOD
1997 elif self.options.ocsp == 'revoked':
1998 ocsp_state = minica.OCSP_STATE_REVOKED
1999 elif self.options.ocsp == 'invalid':
2000 ocsp_state = minica.OCSP_STATE_INVALID
2001 elif self.options.ocsp == 'unauthorized':
2002 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2003 elif self.options.ocsp == 'unknown':
2004 ocsp_state = minica.OCSP_STATE_UNKNOWN
2005 else:
2006 raise testserver_base.OptionError('unknown OCSP status: ' +
2007 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002008
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2010 subject = "127.0.0.1",
2011 ocsp_url = ("http://%s:%d/ocsp" %
2012 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002013 ocsp_state = ocsp_state,
2014 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002015
2016 self.__ocsp_server.ocsp_response = ocsp_der
2017
2018 for ca_cert in self.options.ssl_client_ca:
2019 if not os.path.isfile(ca_cert):
2020 raise testserver_base.OptionError(
2021 'specified trusted client CA file not found: ' + ca_cert +
2022 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002023
2024 stapled_ocsp_response = None
2025 if self.__ocsp_server and self.options.staple_ocsp_response:
2026 stapled_ocsp_response = self.__ocsp_server.ocsp_response
2027
mattm@chromium.org830a3712012-11-07 23:00:07 +00002028 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2029 self.options.ssl_client_auth,
2030 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002031 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002032 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002033 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002034 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002035 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002036 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002037 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002038 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002039 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002040 self.options.fallback_scsv,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002041 stapled_ocsp_response,
2042 self.options.disable_session_cache)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002043 print 'HTTPS server started on https://%s:%d...' % \
2044 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002045 else:
2046 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002047 print 'HTTP server started on http://%s:%d...' % \
2048 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002049
2050 server.data_dir = self.__make_data_dir()
2051 server.file_root_url = self.options.file_root_url
2052 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 elif self.options.server_type == SERVER_WEBSOCKET:
2054 # Launch pywebsocket via WebSocketServer.
2055 logger = logging.getLogger()
2056 logger.addHandler(logging.StreamHandler())
2057 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2058 # is required to work correctly. It should be fixed from pywebsocket side.
2059 os.chdir(self.__make_data_dir())
2060 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002061 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002062 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002063 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002064 websocket_options.use_tls = True
2065 websocket_options.private_key = self.options.cert_and_key_file
2066 websocket_options.certificate = self.options.cert_and_key_file
2067 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002068 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002069 websocket_options.tls_client_auth = True
2070 if len(self.options.ssl_client_ca) != 1:
2071 raise testserver_base.OptionError(
2072 'one trusted client CA file should be specified')
2073 if not os.path.isfile(self.options.ssl_client_ca[0]):
2074 raise testserver_base.OptionError(
2075 'specified trusted client CA file not found: ' +
2076 self.options.ssl_client_ca[0] + ' exiting...')
2077 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2078 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002079 print 'WebSocket server started on %s://%s:%d...' % \
2080 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002081 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002082 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002083 elif self.options.server_type == SERVER_TCP_ECHO:
2084 # Used for generating the key (randomly) that encodes the "echo request"
2085 # message.
2086 random.seed()
2087 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002088 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002089 server_data['port'] = server.server_port
2090 elif self.options.server_type == SERVER_UDP_ECHO:
2091 # Used for generating the key (randomly) that encodes the "echo request"
2092 # message.
2093 random.seed()
2094 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002095 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002096 server_data['port'] = server.server_port
2097 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2098 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002099 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002100 server_data['port'] = server.server_port
2101 elif self.options.server_type == SERVER_FTP:
2102 my_data_dir = self.__make_data_dir()
2103
2104 # Instantiate a dummy authorizer for managing 'virtual' users
2105 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2106
2107 # Define a new user having full r/w permissions and a read-only
2108 # anonymous user
2109 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2110
2111 authorizer.add_anonymous(my_data_dir)
2112
2113 # Instantiate FTP handler class
2114 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2115 ftp_handler.authorizer = authorizer
2116
2117 # Define a customized banner (string returned when client connects)
2118 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2119 pyftpdlib.ftpserver.__ver__)
2120
2121 # Instantiate FTP server class and listen to address:port
2122 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2123 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002124 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002125 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002126 raise testserver_base.OptionError('unknown server type' +
2127 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002128
mattm@chromium.org830a3712012-11-07 23:00:07 +00002129 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002130
mattm@chromium.org830a3712012-11-07 23:00:07 +00002131 def run_server(self):
2132 if self.__ocsp_server:
2133 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002134
mattm@chromium.org830a3712012-11-07 23:00:07 +00002135 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002136
mattm@chromium.org830a3712012-11-07 23:00:07 +00002137 if self.__ocsp_server:
2138 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002139
mattm@chromium.org830a3712012-11-07 23:00:07 +00002140 def add_options(self):
2141 testserver_base.TestServerRunner.add_options(self)
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002142 self.option_parser.add_option('--disable-session-cache',
2143 action='store_true',
2144 dest='disable_session_cache',
2145 help='tells the server to disable the'
2146 'TLS session cache.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002147 self.option_parser.add_option('-f', '--ftp', action='store_const',
2148 const=SERVER_FTP, default=SERVER_HTTP,
2149 dest='server_type',
2150 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002151 self.option_parser.add_option('--tcp-echo', action='store_const',
2152 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2153 dest='server_type',
2154 help='start up a tcp echo server.')
2155 self.option_parser.add_option('--udp-echo', action='store_const',
2156 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2157 dest='server_type',
2158 help='start up a udp echo server.')
2159 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2160 const=SERVER_BASIC_AUTH_PROXY,
2161 default=SERVER_HTTP, dest='server_type',
2162 help='start up a proxy server which requires '
2163 'basic authentication.')
2164 self.option_parser.add_option('--websocket', action='store_const',
2165 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2166 dest='server_type',
2167 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002168 self.option_parser.add_option('--https', action='store_true',
2169 dest='https', help='Specify that https '
2170 'should be used.')
2171 self.option_parser.add_option('--cert-and-key-file',
2172 dest='cert_and_key_file', help='specify the '
2173 'path to the file containing the certificate '
2174 'and private key for the server in PEM '
2175 'format')
2176 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2177 help='The type of OCSP response generated '
2178 'for the automatically generated '
2179 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002180 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2181 default=0, type=int,
2182 help='If non-zero then the generated '
2183 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002184 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2185 default='0', type='int',
2186 help='If nonzero, certain TLS connections '
2187 'will be aborted in order to test version '
2188 'fallback. 1 means all TLS versions will be '
2189 'aborted. 2 means TLS 1.1 or higher will be '
2190 'aborted. 3 means TLS 1.2 or higher will be '
2191 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002192 self.option_parser.add_option('--tls-intolerance-type',
2193 dest='tls_intolerance_type',
2194 default="alert",
2195 help='Controls how the server reacts to a '
2196 'TLS version it is intolerant to. Valid '
2197 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002198 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2199 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002200 default='',
2201 help='Base64 encoded SCT list. If set, '
2202 'server will respond with a '
2203 'signed_certificate_timestamp TLS extension '
2204 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002205 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2206 default=False, const=True,
2207 action='store_const',
2208 help='If given, TLS_FALLBACK_SCSV support '
2209 'will be enabled. This causes the server to '
2210 'reject fallback connections from compatible '
2211 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002212 self.option_parser.add_option('--staple-ocsp-response',
2213 dest='staple_ocsp_response',
2214 default=False, action='store_true',
2215 help='If set, server will staple the OCSP '
2216 'response whenever OCSP is on and the client '
2217 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002218 self.option_parser.add_option('--https-record-resume',
2219 dest='record_resume', const=True,
2220 default=False, action='store_const',
2221 help='Record resumption cache events rather '
2222 'than resuming as normal. Allows the use of '
2223 'the /ssl-session-cache request')
2224 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2225 help='Require SSL client auth on every '
2226 'connection.')
2227 self.option_parser.add_option('--ssl-client-ca', action='append',
2228 default=[], help='Specify that the client '
2229 'certificate request should include the CA '
2230 'named in the subject of the DER-encoded '
2231 'certificate contained in the specified '
2232 'file. This option may appear multiple '
2233 'times, indicating multiple CA names should '
2234 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002235 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2236 default=[], help='Specify that the client '
2237 'certificate request should include the '
2238 'specified certificate_type value. This '
2239 'option may appear multiple times, '
2240 'indicating multiple values should be send '
2241 'in the request. Valid values are '
2242 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2243 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002244 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2245 help='Specify the bulk encryption '
2246 'algorithm(s) that will be accepted by the '
2247 'SSL server. Valid values are "aes256", '
2248 '"aes128", "3des", "rc4". If omitted, all '
2249 'algorithms will be used. This option may '
2250 'appear multiple times, indicating '
2251 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002252 self.option_parser.add_option('--ssl-key-exchange', action='append',
2253 help='Specify the key exchange algorithm(s)'
2254 'that will be accepted by the SSL server. '
2255 'Valid values are "rsa", "dhe_rsa". If '
2256 'omitted, all algorithms will be used. This '
2257 'option may appear multiple times, '
2258 'indicating multiple algorithms should be '
2259 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002260 # TODO(davidben): Add ALPN support to tlslite.
2261 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2262 default=False, const=True,
2263 action='store_const',
2264 help='Enable server support for the NPN '
2265 'extension. The server will advertise '
2266 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002267 self.option_parser.add_option('--file-root-url', default='/files/',
2268 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002269 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2270 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2271 dest='ws_basic_auth',
2272 help='Enable basic-auth for WebSocket')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002273
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002274
initial.commit94958cf2008-07-26 22:42:52 +00002275if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002276 sys.exit(ServerRunner().main())