blob: 0dcbd25a6035a483ffff2d419ce30ede96f41171 [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',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700361 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000362 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000363 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000364 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000365 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000366 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000367 }
initial.commit94958cf2008-07-26 22:42:52 +0000368 self._default_mime_type = 'text/html'
369
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000370 testserver_base.BasePageHandler.__init__(self, request, client_address,
371 socket_server, connect_handlers,
372 get_handlers, head_handlers,
373 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000374
initial.commit94958cf2008-07-26 22:42:52 +0000375 def GetMIMETypeFromName(self, file_name):
376 """Returns the mime type for the specified file_name. So far it only looks
377 at the file extension."""
378
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000379 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000380 if len(extension) == 0:
381 # no extension.
382 return self._default_mime_type
383
ericroman@google.comc17ca532009-05-07 03:51:05 +0000384 # extension starts with a dot, so we need to remove it
385 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000386
initial.commit94958cf2008-07-26 22:42:52 +0000387 def NoCacheMaxAgeTimeHandler(self):
388 """This request handler yields a page with the title set to the current
389 system time, and no caching requested."""
390
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000391 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000392 return False
393
394 self.send_response(200)
395 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000396 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000397 self.end_headers()
398
maruel@google.come250a9b2009-03-10 17:39:46 +0000399 self.wfile.write('<html><head><title>%s</title></head></html>' %
400 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000401
402 return True
403
404 def NoCacheTimeHandler(self):
405 """This request handler yields a page with the title set to the current
406 system time, and no caching requested."""
407
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000408 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000409 return False
410
411 self.send_response(200)
412 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000413 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000414 self.end_headers()
415
maruel@google.come250a9b2009-03-10 17:39:46 +0000416 self.wfile.write('<html><head><title>%s</title></head></html>' %
417 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000418
419 return True
420
421 def CacheTimeHandler(self):
422 """This request handler yields a page with the title set to the current
423 system time, and allows caching for one minute."""
424
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000425 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000426 return False
427
428 self.send_response(200)
429 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000430 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000431 self.end_headers()
432
maruel@google.come250a9b2009-03-10 17:39:46 +0000433 self.wfile.write('<html><head><title>%s</title></head></html>' %
434 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000435
436 return True
437
438 def CacheExpiresHandler(self):
439 """This request handler yields a page with the title set to the current
440 system time, and set the page to expire on 1 Jan 2099."""
441
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000442 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000443 return False
444
445 self.send_response(200)
446 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000447 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000448 self.end_headers()
449
maruel@google.come250a9b2009-03-10 17:39:46 +0000450 self.wfile.write('<html><head><title>%s</title></head></html>' %
451 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000452
453 return True
454
455 def CacheProxyRevalidateHandler(self):
456 """This request handler yields a page with the title set to the current
457 system time, and allows caching for 60 seconds"""
458
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000459 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000460 return False
461
462 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000463 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000464 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
465 self.end_headers()
466
maruel@google.come250a9b2009-03-10 17:39:46 +0000467 self.wfile.write('<html><head><title>%s</title></head></html>' %
468 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000469
470 return True
471
472 def CachePrivateHandler(self):
473 """This request handler yields a page with the title set to the current
474 system time, and allows caching for 5 seconds."""
475
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000476 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000477 return False
478
479 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000480 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000481 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000482 self.end_headers()
483
maruel@google.come250a9b2009-03-10 17:39:46 +0000484 self.wfile.write('<html><head><title>%s</title></head></html>' %
485 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000486
487 return True
488
489 def CachePublicHandler(self):
490 """This request handler yields a page with the title set to the current
491 system time, and allows caching for 5 seconds."""
492
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000493 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000494 return False
495
496 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000497 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000498 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000499 self.end_headers()
500
maruel@google.come250a9b2009-03-10 17:39:46 +0000501 self.wfile.write('<html><head><title>%s</title></head></html>' %
502 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000503
504 return True
505
506 def CacheSMaxAgeHandler(self):
507 """This request handler yields a page with the title set to the current
508 system time, and does not allow for caching."""
509
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000510 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000511 return False
512
513 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000514 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000515 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
516 self.end_headers()
517
maruel@google.come250a9b2009-03-10 17:39:46 +0000518 self.wfile.write('<html><head><title>%s</title></head></html>' %
519 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000520
521 return True
522
523 def CacheMustRevalidateHandler(self):
524 """This request handler yields a page with the title set to the current
525 system time, and does not allow caching."""
526
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000527 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000528 return False
529
530 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000531 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000532 self.send_header('Cache-Control', 'must-revalidate')
533 self.end_headers()
534
maruel@google.come250a9b2009-03-10 17:39:46 +0000535 self.wfile.write('<html><head><title>%s</title></head></html>' %
536 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000537
538 return True
539
540 def CacheMustRevalidateMaxAgeHandler(self):
541 """This request handler yields a page with the title set to the current
542 system time, and does not allow caching event though max-age of 60
543 seconds is specified."""
544
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000545 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000546 return False
547
548 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000549 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000550 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
551 self.end_headers()
552
maruel@google.come250a9b2009-03-10 17:39:46 +0000553 self.wfile.write('<html><head><title>%s</title></head></html>' %
554 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000555
556 return True
557
initial.commit94958cf2008-07-26 22:42:52 +0000558 def CacheNoStoreHandler(self):
559 """This request handler yields a page with the title set to the current
560 system time, and does not allow the page to be stored."""
561
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000562 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000563 return False
564
565 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000566 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000567 self.send_header('Cache-Control', 'no-store')
568 self.end_headers()
569
maruel@google.come250a9b2009-03-10 17:39:46 +0000570 self.wfile.write('<html><head><title>%s</title></head></html>' %
571 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000572
573 return True
574
575 def CacheNoStoreMaxAgeHandler(self):
576 """This request handler yields a page with the title set to the current
577 system time, and does not allow the page to be stored even though max-age
578 of 60 seconds is specified."""
579
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000580 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000581 return False
582
583 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000584 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000585 self.send_header('Cache-Control', 'max-age=60, no-store')
586 self.end_headers()
587
maruel@google.come250a9b2009-03-10 17:39:46 +0000588 self.wfile.write('<html><head><title>%s</title></head></html>' %
589 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000590
591 return True
592
593
594 def CacheNoTransformHandler(self):
595 """This request handler yields a page with the title set to the current
596 system time, and does not allow the content to transformed during
597 user-agent caching"""
598
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000599 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000600 return False
601
602 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000603 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000604 self.send_header('Cache-Control', 'no-transform')
605 self.end_headers()
606
maruel@google.come250a9b2009-03-10 17:39:46 +0000607 self.wfile.write('<html><head><title>%s</title></head></html>' %
608 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000609
610 return True
611
612 def EchoHeader(self):
613 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000614
ananta@chromium.org219b2062009-10-23 16:09:41 +0000615 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000616
ananta@chromium.org56812d02011-04-07 17:52:05 +0000617 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000618 """This function echoes back the value of a specific request header while
619 allowing caching for 16 hours."""
620
ananta@chromium.org56812d02011-04-07 17:52:05 +0000621 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000622
623 def EchoHeaderHelper(self, echo_header):
624 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000625
ananta@chromium.org219b2062009-10-23 16:09:41 +0000626 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000627 return False
628
629 query_char = self.path.find('?')
630 if query_char != -1:
631 header_name = self.path[query_char+1:]
632
633 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000634 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000635 if echo_header == '/echoheadercache':
636 self.send_header('Cache-control', 'max-age=60000')
637 else:
638 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000639 # insert a vary header to properly indicate that the cachability of this
640 # request is subject to value of the request header being echoed.
641 if len(header_name) > 0:
642 self.send_header('Vary', header_name)
643 self.end_headers()
644
645 if len(header_name) > 0:
646 self.wfile.write(self.headers.getheader(header_name))
647
648 return True
649
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000650 def ReadRequestBody(self):
651 """This function reads the body of the current HTTP request, handling
652 both plain and chunked transfer encoded requests."""
653
654 if self.headers.getheader('transfer-encoding') != 'chunked':
655 length = int(self.headers.getheader('content-length'))
656 return self.rfile.read(length)
657
658 # Read the request body as chunks.
659 body = ""
660 while True:
661 line = self.rfile.readline()
662 length = int(line, 16)
663 if length == 0:
664 self.rfile.readline()
665 break
666 body += self.rfile.read(length)
667 self.rfile.read(2)
668 return body
669
initial.commit94958cf2008-07-26 22:42:52 +0000670 def EchoHandler(self):
671 """This handler just echoes back the payload of the request, for testing
672 form submission."""
673
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000674 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000675 return False
676
677 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000678 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000679 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000680 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000681 return True
682
683 def EchoTitleHandler(self):
684 """This handler is like Echo, but sets the page title to the request."""
685
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000686 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000687 return False
688
689 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000690 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000691 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000692 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000693 self.wfile.write('<html><head><title>')
694 self.wfile.write(request)
695 self.wfile.write('</title></head></html>')
696 return True
697
698 def EchoAllHandler(self):
699 """This handler yields a (more) human-readable page listing information
700 about the request header & contents."""
701
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000702 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000703 return False
704
705 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000706 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000707 self.end_headers()
708 self.wfile.write('<html><head><style>'
709 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
710 '</style></head><body>'
711 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000712 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000713 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000714
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000715 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000716 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000717 params = cgi.parse_qs(qs, keep_blank_values=1)
718
719 for param in params:
720 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000721
722 self.wfile.write('</pre>')
723
724 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
725
726 self.wfile.write('</body></html>')
727 return True
728
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000729 def EchoMultipartPostHandler(self):
730 """This handler echoes received multipart post data as json format."""
731
732 if not (self._ShouldHandleRequest("/echomultipartpost") or
733 self._ShouldHandleRequest("/searchbyimage")):
734 return False
735
736 content_type, parameters = cgi.parse_header(
737 self.headers.getheader('content-type'))
738 if content_type == 'multipart/form-data':
739 post_multipart = cgi.parse_multipart(self.rfile, parameters)
740 elif content_type == 'application/x-www-form-urlencoded':
741 raise Exception('POST by application/x-www-form-urlencoded is '
742 'not implemented.')
743 else:
744 post_multipart = {}
745
746 # Since the data can be binary, we encode them by base64.
747 post_multipart_base64_encoded = {}
748 for field, values in post_multipart.items():
749 post_multipart_base64_encoded[field] = [base64.b64encode(value)
750 for value in values]
751
752 result = {'POST_multipart' : post_multipart_base64_encoded}
753
754 self.send_response(200)
755 self.send_header("Content-type", "text/plain")
756 self.end_headers()
757 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
758 return True
759
initial.commit94958cf2008-07-26 22:42:52 +0000760 def DownloadHandler(self):
761 """This handler sends a downloadable file with or without reporting
762 the size (6K)."""
763
764 if self.path.startswith("/download-unknown-size"):
765 send_length = False
766 elif self.path.startswith("/download-known-size"):
767 send_length = True
768 else:
769 return False
770
771 #
772 # The test which uses this functionality is attempting to send
773 # small chunks of data to the client. Use a fairly large buffer
774 # so that we'll fill chrome's IO buffer enough to force it to
775 # actually write the data.
776 # See also the comments in the client-side of this test in
777 # download_uitest.cc
778 #
779 size_chunk1 = 35*1024
780 size_chunk2 = 10*1024
781
782 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000783 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000784 self.send_header('Cache-Control', 'max-age=0')
785 if send_length:
786 self.send_header('Content-Length', size_chunk1 + size_chunk2)
787 self.end_headers()
788
789 # First chunk of data:
790 self.wfile.write("*" * size_chunk1)
791 self.wfile.flush()
792
793 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000794 self.server.wait_for_download = True
795 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000796 self.server.handle_request()
797
798 # Second chunk of data:
799 self.wfile.write("*" * size_chunk2)
800 return True
801
802 def DownloadFinishHandler(self):
803 """This handler just tells the server to finish the current download."""
804
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000805 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000806 return False
807
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000808 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000809 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000810 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000811 self.send_header('Cache-Control', 'max-age=0')
812 self.end_headers()
813 return True
814
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000815 def _ReplaceFileData(self, data, query_parameters):
816 """Replaces matching substrings in a file.
817
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000818 If the 'replace_text' URL query parameter is present, it is expected to be
819 of the form old_text:new_text, which indicates that any old_text strings in
820 the file are replaced with new_text. Multiple 'replace_text' parameters may
821 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000822
823 If the parameters are not present, |data| is returned.
824 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000825
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000826 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000827 replace_text_values = query_dict.get('replace_text', [])
828 for replace_text_value in replace_text_values:
829 replace_text_args = replace_text_value.split(':')
830 if len(replace_text_args) != 2:
831 raise ValueError(
832 'replace_text must be of form old_text:new_text. Actual value: %s' %
833 replace_text_value)
834 old_text_b64, new_text_b64 = replace_text_args
835 old_text = base64.urlsafe_b64decode(old_text_b64)
836 new_text = base64.urlsafe_b64decode(new_text_b64)
837 data = data.replace(old_text, new_text)
838 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000839
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000840 def ZipFileHandler(self):
841 """This handler sends the contents of the requested file in compressed form.
842 Can pass in a parameter that specifies that the content length be
843 C - the compressed size (OK),
844 U - the uncompressed size (Non-standard, but handled),
845 S - less than compressed (OK because we keep going),
846 M - larger than compressed but less than uncompressed (an error),
847 L - larger than uncompressed (an error)
848 Example: compressedfiles/Picture_1.doc?C
849 """
850
851 prefix = "/compressedfiles/"
852 if not self.path.startswith(prefix):
853 return False
854
855 # Consume a request body if present.
856 if self.command == 'POST' or self.command == 'PUT' :
857 self.ReadRequestBody()
858
859 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
860
861 if not query in ('C', 'U', 'S', 'M', 'L'):
862 return False
863
864 sub_path = url_path[len(prefix):]
865 entries = sub_path.split('/')
866 file_path = os.path.join(self.server.data_dir, *entries)
867 if os.path.isdir(file_path):
868 file_path = os.path.join(file_path, 'index.html')
869
870 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000871 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000872 self.send_error(404)
873 return True
874
875 f = open(file_path, "rb")
876 data = f.read()
877 uncompressed_len = len(data)
878 f.close()
879
880 # Compress the data.
881 data = zlib.compress(data)
882 compressed_len = len(data)
883
884 content_length = compressed_len
885 if query == 'U':
886 content_length = uncompressed_len
887 elif query == 'S':
888 content_length = compressed_len / 2
889 elif query == 'M':
890 content_length = (compressed_len + uncompressed_len) / 2
891 elif query == 'L':
892 content_length = compressed_len + uncompressed_len
893
894 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000895 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000896 self.send_header('Content-encoding', 'deflate')
897 self.send_header('Connection', 'close')
898 self.send_header('Content-Length', content_length)
899 self.send_header('ETag', '\'' + file_path + '\'')
900 self.end_headers()
901
902 self.wfile.write(data)
903
904 return True
905
initial.commit94958cf2008-07-26 22:42:52 +0000906 def FileHandler(self):
907 """This handler sends the contents of the requested file. Wow, it's like
908 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000909
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000910 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000911 if not self.path.startswith(prefix):
912 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000913 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000914
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000915 def PostOnlyFileHandler(self):
916 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000917
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000918 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000919 if not self.path.startswith(prefix):
920 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000921 return self._FileHandlerHelper(prefix)
922
923 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000924 request_body = ''
925 if self.command == 'POST' or self.command == 'PUT':
926 # Consume a request body if present.
927 request_body = self.ReadRequestBody()
928
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000929 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000930 query_dict = cgi.parse_qs(query)
931
932 expected_body = query_dict.get('expected_body', [])
933 if expected_body and request_body not in expected_body:
934 self.send_response(404)
935 self.end_headers()
936 self.wfile.write('')
937 return True
938
939 expected_headers = query_dict.get('expected_headers', [])
940 for expected_header in expected_headers:
941 header_name, expected_value = expected_header.split(':')
942 if self.headers.getheader(header_name) != expected_value:
943 self.send_response(404)
944 self.end_headers()
945 self.wfile.write('')
946 return True
947
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000948 sub_path = url_path[len(prefix):]
949 entries = sub_path.split('/')
950 file_path = os.path.join(self.server.data_dir, *entries)
951 if os.path.isdir(file_path):
952 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000953
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000954 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000955 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000956 self.send_error(404)
957 return True
958
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000959 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000960 data = f.read()
961 f.close()
962
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000963 data = self._ReplaceFileData(data, query)
964
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000965 old_protocol_version = self.protocol_version
966
initial.commit94958cf2008-07-26 22:42:52 +0000967 # If file.mock-http-headers exists, it contains the headers we
968 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000969 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000970 if os.path.isfile(headers_path):
971 f = open(headers_path, "r")
972
973 # "HTTP/1.1 200 OK"
974 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000975 http_major, http_minor, status_code = re.findall(
976 'HTTP/(\d+).(\d+) (\d+)', response)[0]
977 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000978 self.send_response(int(status_code))
979
980 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000981 header_values = re.findall('(\S+):\s*(.*)', line)
982 if len(header_values) > 0:
983 # "name: value"
984 name, value = header_values[0]
985 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000986 f.close()
987 else:
988 # Could be more generic once we support mime-type sniffing, but for
989 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000990
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000991 range_header = self.headers.get('Range')
992 if range_header and range_header.startswith('bytes='):
993 # Note this doesn't handle all valid byte range_header values (i.e.
994 # left open ended ones), just enough for what we needed so far.
995 range_header = range_header[6:].split('-')
996 start = int(range_header[0])
997 if range_header[1]:
998 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +0000999 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001000 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001001
1002 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001003 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1004 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001005 self.send_header('Content-Range', content_range)
1006 data = data[start: end + 1]
1007 else:
1008 self.send_response(200)
1009
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001010 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001011 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001012 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001013 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001014 self.end_headers()
1015
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001016 if (self.command != 'HEAD'):
1017 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001018
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001019 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001020 return True
1021
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001022 def SetCookieHandler(self):
1023 """This handler just sets a cookie, for testing cookie handling."""
1024
1025 if not self._ShouldHandleRequest("/set-cookie"):
1026 return False
1027
1028 query_char = self.path.find('?')
1029 if query_char != -1:
1030 cookie_values = self.path[query_char + 1:].split('&')
1031 else:
1032 cookie_values = ("",)
1033 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001034 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001035 for cookie_value in cookie_values:
1036 self.send_header('Set-Cookie', '%s' % cookie_value)
1037 self.end_headers()
1038 for cookie_value in cookie_values:
1039 self.wfile.write('%s' % cookie_value)
1040 return True
1041
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001042 def SetManyCookiesHandler(self):
1043 """This handler just sets a given number of cookies, for testing handling
1044 of large numbers of cookies."""
1045
1046 if not self._ShouldHandleRequest("/set-many-cookies"):
1047 return False
1048
1049 query_char = self.path.find('?')
1050 if query_char != -1:
1051 num_cookies = int(self.path[query_char + 1:])
1052 else:
1053 num_cookies = 0
1054 self.send_response(200)
1055 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001056 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001057 self.send_header('Set-Cookie', 'a=')
1058 self.end_headers()
1059 self.wfile.write('%d cookies were sent' % num_cookies)
1060 return True
1061
mattm@chromium.org983fc462012-06-30 00:52:08 +00001062 def ExpectAndSetCookieHandler(self):
1063 """Expects some cookies to be sent, and if they are, sets more cookies.
1064
1065 The expect parameter specifies a required cookie. May be specified multiple
1066 times.
1067 The set parameter specifies a cookie to set if all required cookies are
1068 preset. May be specified multiple times.
1069 The data parameter specifies the response body data to be returned."""
1070
1071 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1072 return False
1073
1074 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1075 query_dict = cgi.parse_qs(query)
1076 cookies = set()
1077 if 'Cookie' in self.headers:
1078 cookie_header = self.headers.getheader('Cookie')
1079 cookies.update([s.strip() for s in cookie_header.split(';')])
1080 got_all_expected_cookies = True
1081 for expected_cookie in query_dict.get('expect', []):
1082 if expected_cookie not in cookies:
1083 got_all_expected_cookies = False
1084 self.send_response(200)
1085 self.send_header('Content-Type', 'text/html')
1086 if got_all_expected_cookies:
1087 for cookie_value in query_dict.get('set', []):
1088 self.send_header('Set-Cookie', '%s' % cookie_value)
1089 self.end_headers()
1090 for data_value in query_dict.get('data', []):
1091 self.wfile.write(data_value)
1092 return True
1093
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001094 def SetHeaderHandler(self):
1095 """This handler sets a response header. Parameters are in the
1096 key%3A%20value&key2%3A%20value2 format."""
1097
1098 if not self._ShouldHandleRequest("/set-header"):
1099 return False
1100
1101 query_char = self.path.find('?')
1102 if query_char != -1:
1103 headers_values = self.path[query_char + 1:].split('&')
1104 else:
1105 headers_values = ("",)
1106 self.send_response(200)
1107 self.send_header('Content-Type', 'text/html')
1108 for header_value in headers_values:
1109 header_value = urllib.unquote(header_value)
1110 (key, value) = header_value.split(': ', 1)
1111 self.send_header(key, value)
1112 self.end_headers()
1113 for header_value in headers_values:
1114 self.wfile.write('%s' % header_value)
1115 return True
1116
initial.commit94958cf2008-07-26 22:42:52 +00001117 def AuthBasicHandler(self):
1118 """This handler tests 'Basic' authentication. It just sends a page with
1119 title 'user/pass' if you succeed."""
1120
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001121 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001122 return False
1123
1124 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001125 expected_password = 'secret'
1126 realm = 'testrealm'
1127 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001128
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001129 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1130 query_params = cgi.parse_qs(query, True)
1131 if 'set-cookie-if-challenged' in query_params:
1132 set_cookie_if_challenged = True
1133 if 'password' in query_params:
1134 expected_password = query_params['password'][0]
1135 if 'realm' in query_params:
1136 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001137
initial.commit94958cf2008-07-26 22:42:52 +00001138 auth = self.headers.getheader('authorization')
1139 try:
1140 if not auth:
1141 raise Exception('no auth')
1142 b64str = re.findall(r'Basic (\S+)', auth)[0]
1143 userpass = base64.b64decode(b64str)
1144 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001145 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001146 raise Exception('wrong password')
1147 except Exception, e:
1148 # Authentication failed.
1149 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001150 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001151 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001152 if set_cookie_if_challenged:
1153 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001154 self.end_headers()
1155 self.wfile.write('<html><head>')
1156 self.wfile.write('<title>Denied: %s</title>' % e)
1157 self.wfile.write('</head><body>')
1158 self.wfile.write('auth=%s<p>' % auth)
1159 self.wfile.write('b64str=%s<p>' % b64str)
1160 self.wfile.write('username: %s<p>' % username)
1161 self.wfile.write('userpass: %s<p>' % userpass)
1162 self.wfile.write('password: %s<p>' % password)
1163 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1164 self.wfile.write('</body></html>')
1165 return True
1166
1167 # Authentication successful. (Return a cachable response to allow for
1168 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001169 old_protocol_version = self.protocol_version
1170 self.protocol_version = "HTTP/1.1"
1171
initial.commit94958cf2008-07-26 22:42:52 +00001172 if_none_match = self.headers.getheader('if-none-match')
1173 if if_none_match == "abc":
1174 self.send_response(304)
1175 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001176 elif url_path.endswith(".gif"):
1177 # Using chrome/test/data/google/logo.gif as the test image
1178 test_image_path = ['google', 'logo.gif']
1179 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1180 if not os.path.isfile(gif_path):
1181 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001182 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001183 return True
1184
1185 f = open(gif_path, "rb")
1186 data = f.read()
1187 f.close()
1188
1189 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001190 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001191 self.send_header('Cache-control', 'max-age=60000')
1192 self.send_header('Etag', 'abc')
1193 self.end_headers()
1194 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001195 else:
1196 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001197 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001198 self.send_header('Cache-control', 'max-age=60000')
1199 self.send_header('Etag', 'abc')
1200 self.end_headers()
1201 self.wfile.write('<html><head>')
1202 self.wfile.write('<title>%s/%s</title>' % (username, password))
1203 self.wfile.write('</head><body>')
1204 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001205 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001206 self.wfile.write('</body></html>')
1207
rvargas@google.com54453b72011-05-19 01:11:11 +00001208 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001209 return True
1210
tonyg@chromium.org75054202010-03-31 22:06:10 +00001211 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001212 """Returns a nonce that's stable per request path for the server's lifetime.
1213 This is a fake implementation. A real implementation would only use a given
1214 nonce a single time (hence the name n-once). However, for the purposes of
1215 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001216
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001217 Args:
1218 force_reset: Iff set, the nonce will be changed. Useful for testing the
1219 "stale" response.
1220 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001221
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001222 if force_reset or not self.server.nonce_time:
1223 self.server.nonce_time = time.time()
1224 return hashlib.md5('privatekey%s%d' %
1225 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001226
1227 def AuthDigestHandler(self):
1228 """This handler tests 'Digest' authentication.
1229
1230 It just sends a page with title 'user/pass' if you succeed.
1231
1232 A stale response is sent iff "stale" is present in the request path.
1233 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001234
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001235 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001236 return False
1237
tonyg@chromium.org75054202010-03-31 22:06:10 +00001238 stale = 'stale' in self.path
1239 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001240 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001241 password = 'secret'
1242 realm = 'testrealm'
1243
1244 auth = self.headers.getheader('authorization')
1245 pairs = {}
1246 try:
1247 if not auth:
1248 raise Exception('no auth')
1249 if not auth.startswith('Digest'):
1250 raise Exception('not digest')
1251 # Pull out all the name="value" pairs as a dictionary.
1252 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1253
1254 # Make sure it's all valid.
1255 if pairs['nonce'] != nonce:
1256 raise Exception('wrong nonce')
1257 if pairs['opaque'] != opaque:
1258 raise Exception('wrong opaque')
1259
1260 # Check the 'response' value and make sure it matches our magic hash.
1261 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001262 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001263 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001264 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001265 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001266 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001267 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1268 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001269 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001270
1271 if pairs['response'] != response:
1272 raise Exception('wrong password')
1273 except Exception, e:
1274 # Authentication failed.
1275 self.send_response(401)
1276 hdr = ('Digest '
1277 'realm="%s", '
1278 'domain="/", '
1279 'qop="auth", '
1280 'algorithm=MD5, '
1281 'nonce="%s", '
1282 'opaque="%s"') % (realm, nonce, opaque)
1283 if stale:
1284 hdr += ', stale="TRUE"'
1285 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001286 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001287 self.end_headers()
1288 self.wfile.write('<html><head>')
1289 self.wfile.write('<title>Denied: %s</title>' % e)
1290 self.wfile.write('</head><body>')
1291 self.wfile.write('auth=%s<p>' % auth)
1292 self.wfile.write('pairs=%s<p>' % pairs)
1293 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1294 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1295 self.wfile.write('</body></html>')
1296 return True
1297
1298 # Authentication successful.
1299 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001300 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001301 self.end_headers()
1302 self.wfile.write('<html><head>')
1303 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1304 self.wfile.write('</head><body>')
1305 self.wfile.write('auth=%s<p>' % auth)
1306 self.wfile.write('pairs=%s<p>' % pairs)
1307 self.wfile.write('</body></html>')
1308
1309 return True
1310
1311 def SlowServerHandler(self):
1312 """Wait for the user suggested time before responding. The syntax is
1313 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001314
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001315 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001316 return False
1317 query_char = self.path.find('?')
1318 wait_sec = 1.0
1319 if query_char >= 0:
1320 try:
1321 wait_sec = int(self.path[query_char + 1:])
1322 except ValueError:
1323 pass
1324 time.sleep(wait_sec)
1325 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001326 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001327 self.end_headers()
1328 self.wfile.write("waited %d seconds" % wait_sec)
1329 return True
1330
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001331 def ChunkedServerHandler(self):
1332 """Send chunked response. Allows to specify chunks parameters:
1333 - waitBeforeHeaders - ms to wait before sending headers
1334 - waitBetweenChunks - ms to wait between chunks
1335 - chunkSize - size of each chunk in bytes
1336 - chunksNumber - number of chunks
1337 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1338 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001339
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001340 if not self._ShouldHandleRequest("/chunked"):
1341 return False
1342 query_char = self.path.find('?')
1343 chunkedSettings = {'waitBeforeHeaders' : 0,
1344 'waitBetweenChunks' : 0,
1345 'chunkSize' : 5,
1346 'chunksNumber' : 5}
1347 if query_char >= 0:
1348 params = self.path[query_char + 1:].split('&')
1349 for param in params:
1350 keyValue = param.split('=')
1351 if len(keyValue) == 2:
1352 try:
1353 chunkedSettings[keyValue[0]] = int(keyValue[1])
1354 except ValueError:
1355 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001356 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001357 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1358 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001359 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001360 self.send_header('Connection', 'close')
1361 self.send_header('Transfer-Encoding', 'chunked')
1362 self.end_headers()
1363 # Chunked encoding: sending all chunks, then final zero-length chunk and
1364 # then final CRLF.
1365 for i in range(0, chunkedSettings['chunksNumber']):
1366 if i > 0:
1367 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1368 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001369 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001370 self.sendChunkHelp('')
1371 return True
1372
initial.commit94958cf2008-07-26 22:42:52 +00001373 def ContentTypeHandler(self):
1374 """Returns a string of html with the given content type. E.g.,
1375 /contenttype?text/css returns an html file with the Content-Type
1376 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001377
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001378 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001379 return False
1380 query_char = self.path.find('?')
1381 content_type = self.path[query_char + 1:].strip()
1382 if not content_type:
1383 content_type = 'text/html'
1384 self.send_response(200)
1385 self.send_header('Content-Type', content_type)
1386 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001387 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001388 return True
1389
creis@google.com2f4f6a42011-03-25 19:44:19 +00001390 def NoContentHandler(self):
1391 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001392
creis@google.com2f4f6a42011-03-25 19:44:19 +00001393 if not self._ShouldHandleRequest("/nocontent"):
1394 return False
1395 self.send_response(204)
1396 self.end_headers()
1397 return True
1398
initial.commit94958cf2008-07-26 22:42:52 +00001399 def ServerRedirectHandler(self):
1400 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001401 '/server-redirect?http://foo.bar/asdf' to redirect to
1402 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001403
1404 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001405 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001406 return False
1407
1408 query_char = self.path.find('?')
1409 if query_char < 0 or len(self.path) <= query_char + 1:
1410 self.sendRedirectHelp(test_name)
1411 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001412 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001413
1414 self.send_response(301) # moved permanently
1415 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001416 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001417 self.end_headers()
1418 self.wfile.write('<html><head>')
1419 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1420
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001421 return True
initial.commit94958cf2008-07-26 22:42:52 +00001422
naskoe7a0d0d2014-09-29 08:53:05 -07001423 def CrossSiteRedirectHandler(self):
1424 """Sends a server redirect to the given site. The syntax is
1425 '/cross-site/hostname/...' to redirect to //hostname/...
1426 It is used to navigate between different Sites, causing
1427 cross-site/cross-process navigations in the browser."""
1428
1429 test_name = "/cross-site"
1430 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001431 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
davidben11682512014-10-06 21:09:11 -07001539 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1540 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001541 return True
1542
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001543 def CloseSocketHandler(self):
1544 """Closes the socket without sending anything."""
1545
1546 if not self._ShouldHandleRequest('/close-socket'):
1547 return False
1548
1549 self.wfile.close()
1550 return True
1551
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001552 def RangeResetHandler(self):
1553 """Send data broken up by connection resets every N (default 4K) bytes.
1554 Support range requests. If the data requested doesn't straddle a reset
1555 boundary, it will all be sent. Used for testing resuming downloads."""
1556
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001557 def DataForRange(start, end):
1558 """Data to be provided for a particular range of bytes."""
1559 # Offset and scale to avoid too obvious (and hence potentially
1560 # collidable) data.
1561 return ''.join([chr(y % 256)
1562 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1563
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001564 if not self._ShouldHandleRequest('/rangereset'):
1565 return False
1566
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001567 # HTTP/1.1 is required for ETag and range support.
1568 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001569 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1570
1571 # Defaults
1572 size = 8000
1573 # Note that the rst is sent just before sending the rst_boundary byte.
1574 rst_boundary = 4000
1575 respond_to_range = True
1576 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001577 rst_limit = -1
1578 token = 'DEFAULT'
1579 fail_precondition = 0
1580 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001581
1582 # Parse the query
1583 qdict = urlparse.parse_qs(query, True)
1584 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001585 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001586 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001587 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001588 if 'token' in qdict:
1589 # Identifying token for stateful tests.
1590 token = qdict['token'][0]
1591 if 'rst_limit' in qdict:
1592 # Max number of rsts for a given token.
1593 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001594 if 'bounce_range' in qdict:
1595 respond_to_range = False
1596 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001597 # Note that hold_for_signal will not work with null range requests;
1598 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001599 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001600 if 'no_verifiers' in qdict:
1601 send_verifiers = False
1602 if 'fail_precondition' in qdict:
1603 fail_precondition = int(qdict['fail_precondition'][0])
1604
1605 # Record already set information, or set it.
1606 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1607 if rst_limit != 0:
1608 TestPageHandler.rst_limits[token] -= 1
1609 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1610 token, fail_precondition)
1611 if fail_precondition != 0:
1612 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001613
1614 first_byte = 0
1615 last_byte = size - 1
1616
1617 # Does that define what we want to return, or do we need to apply
1618 # a range?
1619 range_response = False
1620 range_header = self.headers.getheader('range')
1621 if range_header and respond_to_range:
1622 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1623 if mo.group(1):
1624 first_byte = int(mo.group(1))
1625 if mo.group(2):
1626 last_byte = int(mo.group(2))
1627 if last_byte > size - 1:
1628 last_byte = size - 1
1629 range_response = True
1630 if last_byte < first_byte:
1631 return False
1632
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001633 if (fail_precondition and
1634 (self.headers.getheader('If-Modified-Since') or
1635 self.headers.getheader('If-Match'))):
1636 self.send_response(412)
1637 self.end_headers()
1638 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001639
1640 if range_response:
1641 self.send_response(206)
1642 self.send_header('Content-Range',
1643 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1644 else:
1645 self.send_response(200)
1646 self.send_header('Content-Type', 'application/octet-stream')
1647 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001648 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001649 # If fail_precondition is non-zero, then the ETag for each request will be
1650 # different.
1651 etag = "%s%d" % (token, fail_precondition)
1652 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001653 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001654 self.end_headers()
1655
1656 if hold_for_signal:
1657 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1658 # a single byte, the self.server.handle_request() below hangs
1659 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001660 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001661 first_byte = first_byte + 1
1662 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001663 self.server.wait_for_download = True
1664 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001665 self.server.handle_request()
1666
1667 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001668 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001669 # No RST has been requested in this range, so we don't need to
1670 # do anything fancy; just write the data and let the python
1671 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001672 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001673 self.wfile.flush()
1674 return True
1675
1676 # We're resetting the connection part way in; go to the RST
1677 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001678 # Because socket semantics do not guarantee that all the data will be
1679 # sent when using the linger semantics to hard close a socket,
1680 # we send the data and then wait for our peer to release us
1681 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001682 data = DataForRange(first_byte, possible_rst)
1683 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001684 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001685 self.server.wait_for_download = True
1686 while self.server.wait_for_download:
1687 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001688 l_onoff = 1 # Linger is active.
1689 l_linger = 0 # Seconds to linger for.
1690 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1691 struct.pack('ii', l_onoff, l_linger))
1692
1693 # Close all duplicates of the underlying socket to force the RST.
1694 self.wfile.close()
1695 self.rfile.close()
1696 self.connection.close()
1697
1698 return True
1699
initial.commit94958cf2008-07-26 22:42:52 +00001700 def DefaultResponseHandler(self):
1701 """This is the catch-all response handler for requests that aren't handled
1702 by one of the special handlers above.
1703 Note that we specify the content-length as without it the https connection
1704 is not closed properly (and the browser keeps expecting data)."""
1705
1706 contents = "Default response given for path: " + self.path
1707 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001708 self.send_header('Content-Type', 'text/html')
1709 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001710 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001711 if (self.command != 'HEAD'):
1712 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001713 return True
1714
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001715 def RedirectConnectHandler(self):
1716 """Sends a redirect to the CONNECT request for www.redirect.com. This
1717 response is not specified by the RFC, so the browser should not follow
1718 the redirect."""
1719
1720 if (self.path.find("www.redirect.com") < 0):
1721 return False
1722
1723 dest = "http://www.destination.com/foo.js"
1724
1725 self.send_response(302) # moved temporarily
1726 self.send_header('Location', dest)
1727 self.send_header('Connection', 'close')
1728 self.end_headers()
1729 return True
1730
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001731 def ServerAuthConnectHandler(self):
1732 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1733 response doesn't make sense because the proxy server cannot request
1734 server authentication."""
1735
1736 if (self.path.find("www.server-auth.com") < 0):
1737 return False
1738
1739 challenge = 'Basic realm="WallyWorld"'
1740
1741 self.send_response(401) # unauthorized
1742 self.send_header('WWW-Authenticate', challenge)
1743 self.send_header('Connection', 'close')
1744 self.end_headers()
1745 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001746
1747 def DefaultConnectResponseHandler(self):
1748 """This is the catch-all response handler for CONNECT requests that aren't
1749 handled by one of the special handlers above. Real Web servers respond
1750 with 400 to CONNECT requests."""
1751
1752 contents = "Your client has issued a malformed or illegal request."
1753 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001754 self.send_header('Content-Type', 'text/html')
1755 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001756 self.end_headers()
1757 self.wfile.write(contents)
1758 return True
1759
initial.commit94958cf2008-07-26 22:42:52 +00001760 # called by the redirect handling function when there is no parameter
1761 def sendRedirectHelp(self, redirect_name):
1762 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001763 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001764 self.end_headers()
1765 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1766 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1767 self.wfile.write('</body></html>')
1768
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001769 # called by chunked handling function
1770 def sendChunkHelp(self, chunk):
1771 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1772 self.wfile.write('%X\r\n' % len(chunk))
1773 self.wfile.write(chunk)
1774 self.wfile.write('\r\n')
1775
akalin@chromium.org154bb132010-11-12 02:20:27 +00001776
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001777class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001778 def __init__(self, request, client_address, socket_server):
1779 handlers = [self.OCSPResponse]
1780 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001781 testserver_base.BasePageHandler.__init__(self, request, client_address,
1782 socket_server, [], handlers, [],
1783 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001784
1785 def OCSPResponse(self):
1786 self.send_response(200)
1787 self.send_header('Content-Type', 'application/ocsp-response')
1788 self.send_header('Content-Length', str(len(self.ocsp_response)))
1789 self.end_headers()
1790
1791 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001792
mattm@chromium.org830a3712012-11-07 23:00:07 +00001793
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001794class TCPEchoHandler(SocketServer.BaseRequestHandler):
1795 """The RequestHandler class for TCP echo server.
1796
1797 It is instantiated once per connection to the server, and overrides the
1798 handle() method to implement communication to the client.
1799 """
1800
1801 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001802 """Handles the request from the client and constructs a response."""
1803
1804 data = self.request.recv(65536).strip()
1805 # Verify the "echo request" message received from the client. Send back
1806 # "echo response" message if "echo request" message is valid.
1807 try:
1808 return_data = echo_message.GetEchoResponseData(data)
1809 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001810 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001811 except ValueError:
1812 return
1813
1814 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001815
1816
1817class UDPEchoHandler(SocketServer.BaseRequestHandler):
1818 """The RequestHandler class for UDP echo server.
1819
1820 It is instantiated once per connection to the server, and overrides the
1821 handle() method to implement communication to the client.
1822 """
1823
1824 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001825 """Handles the request from the client and constructs a response."""
1826
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001827 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001828 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001829 # Verify the "echo request" message received from the client. Send back
1830 # "echo response" message if "echo request" message is valid.
1831 try:
1832 return_data = echo_message.GetEchoResponseData(data)
1833 if not return_data:
1834 return
1835 except ValueError:
1836 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001837 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001838
1839
bashi@chromium.org33233532012-09-08 17:37:24 +00001840class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1841 """A request handler that behaves as a proxy server which requires
1842 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1843 """
1844
1845 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1846
1847 def parse_request(self):
1848 """Overrides parse_request to check credential."""
1849
1850 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1851 return False
1852
1853 auth = self.headers.getheader('Proxy-Authorization')
1854 if auth != self._AUTH_CREDENTIAL:
1855 self.send_response(407)
1856 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1857 self.end_headers()
1858 return False
1859
1860 return True
1861
1862 def _start_read_write(self, sock):
1863 sock.setblocking(0)
1864 self.request.setblocking(0)
1865 rlist = [self.request, sock]
1866 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001867 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001868 if errors:
1869 self.send_response(500)
1870 self.end_headers()
1871 return
1872 for s in ready_sockets:
1873 received = s.recv(1024)
1874 if len(received) == 0:
1875 return
1876 if s == self.request:
1877 other = sock
1878 else:
1879 other = self.request
1880 other.send(received)
1881
1882 def _do_common_method(self):
1883 url = urlparse.urlparse(self.path)
1884 port = url.port
1885 if not port:
1886 if url.scheme == 'http':
1887 port = 80
1888 elif url.scheme == 'https':
1889 port = 443
1890 if not url.hostname or not port:
1891 self.send_response(400)
1892 self.end_headers()
1893 return
1894
1895 if len(url.path) == 0:
1896 path = '/'
1897 else:
1898 path = url.path
1899 if len(url.query) > 0:
1900 path = '%s?%s' % (url.path, url.query)
1901
1902 sock = None
1903 try:
1904 sock = socket.create_connection((url.hostname, port))
1905 sock.send('%s %s %s\r\n' % (
1906 self.command, path, self.protocol_version))
1907 for header in self.headers.headers:
1908 header = header.strip()
1909 if (header.lower().startswith('connection') or
1910 header.lower().startswith('proxy')):
1911 continue
1912 sock.send('%s\r\n' % header)
1913 sock.send('\r\n')
1914 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001915 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001916 self.send_response(500)
1917 self.end_headers()
1918 finally:
1919 if sock is not None:
1920 sock.close()
1921
1922 def do_CONNECT(self):
1923 try:
1924 pos = self.path.rfind(':')
1925 host = self.path[:pos]
1926 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001927 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001928 self.send_response(400)
1929 self.end_headers()
1930
1931 try:
1932 sock = socket.create_connection((host, port))
1933 self.send_response(200, 'Connection established')
1934 self.end_headers()
1935 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001936 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001937 self.send_response(500)
1938 self.end_headers()
1939 finally:
1940 sock.close()
1941
1942 def do_GET(self):
1943 self._do_common_method()
1944
1945 def do_HEAD(self):
1946 self._do_common_method()
1947
1948
mattm@chromium.org830a3712012-11-07 23:00:07 +00001949class ServerRunner(testserver_base.TestServerRunner):
1950 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001951
mattm@chromium.org830a3712012-11-07 23:00:07 +00001952 def __init__(self):
1953 super(ServerRunner, self).__init__()
1954 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001955
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956 def __make_data_dir(self):
1957 if self.options.data_dir:
1958 if not os.path.isdir(self.options.data_dir):
1959 raise testserver_base.OptionError('specified data dir not found: ' +
1960 self.options.data_dir + ' exiting...')
1961 my_data_dir = self.options.data_dir
1962 else:
1963 # Create the default path to our data dir, relative to the exe dir.
1964 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1965 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001966
mattm@chromium.org830a3712012-11-07 23:00:07 +00001967 #TODO(ibrar): Must use Find* funtion defined in google\tools
1968 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001969
mattm@chromium.org830a3712012-11-07 23:00:07 +00001970 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001971
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 def create_server(self, server_data):
1973 port = self.options.port
1974 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001975
mattm@chromium.org830a3712012-11-07 23:00:07 +00001976 if self.options.server_type == SERVER_HTTP:
1977 if self.options.https:
1978 pem_cert_and_key = None
1979 if self.options.cert_and_key_file:
1980 if not os.path.isfile(self.options.cert_and_key_file):
1981 raise testserver_base.OptionError(
1982 'specified server cert file not found: ' +
1983 self.options.cert_and_key_file + ' exiting...')
1984 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001985 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001986 # generate a new certificate and run an OCSP server for it.
1987 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001988 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001989 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001990
mattm@chromium.org830a3712012-11-07 23:00:07 +00001991 ocsp_der = None
1992 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001993
mattm@chromium.org830a3712012-11-07 23:00:07 +00001994 if self.options.ocsp == 'ok':
1995 ocsp_state = minica.OCSP_STATE_GOOD
1996 elif self.options.ocsp == 'revoked':
1997 ocsp_state = minica.OCSP_STATE_REVOKED
1998 elif self.options.ocsp == 'invalid':
1999 ocsp_state = minica.OCSP_STATE_INVALID
2000 elif self.options.ocsp == 'unauthorized':
2001 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2002 elif self.options.ocsp == 'unknown':
2003 ocsp_state = minica.OCSP_STATE_UNKNOWN
2004 else:
2005 raise testserver_base.OptionError('unknown OCSP status: ' +
2006 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002007
mattm@chromium.org830a3712012-11-07 23:00:07 +00002008 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2009 subject = "127.0.0.1",
2010 ocsp_url = ("http://%s:%d/ocsp" %
2011 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002012 ocsp_state = ocsp_state,
2013 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002014
2015 self.__ocsp_server.ocsp_response = ocsp_der
2016
2017 for ca_cert in self.options.ssl_client_ca:
2018 if not os.path.isfile(ca_cert):
2019 raise testserver_base.OptionError(
2020 'specified trusted client CA file not found: ' + ca_cert +
2021 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002022
2023 stapled_ocsp_response = None
2024 if self.__ocsp_server and self.options.staple_ocsp_response:
2025 stapled_ocsp_response = self.__ocsp_server.ocsp_response
2026
mattm@chromium.org830a3712012-11-07 23:00:07 +00002027 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2028 self.options.ssl_client_auth,
2029 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002030 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002031 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002032 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002033 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002034 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002035 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002036 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002037 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002038 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002039 self.options.fallback_scsv,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002040 stapled_ocsp_response,
2041 self.options.disable_session_cache)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002042 print 'HTTPS server started on https://%s:%d...' % \
2043 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002044 else:
2045 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002046 print 'HTTP server started on http://%s:%d...' % \
2047 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002048
2049 server.data_dir = self.__make_data_dir()
2050 server.file_root_url = self.options.file_root_url
2051 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002052 elif self.options.server_type == SERVER_WEBSOCKET:
2053 # Launch pywebsocket via WebSocketServer.
2054 logger = logging.getLogger()
2055 logger.addHandler(logging.StreamHandler())
2056 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2057 # is required to work correctly. It should be fixed from pywebsocket side.
2058 os.chdir(self.__make_data_dir())
2059 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002060 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002061 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002062 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 websocket_options.use_tls = True
2064 websocket_options.private_key = self.options.cert_and_key_file
2065 websocket_options.certificate = self.options.cert_and_key_file
2066 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002067 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002068 websocket_options.tls_client_auth = True
2069 if len(self.options.ssl_client_ca) != 1:
2070 raise testserver_base.OptionError(
2071 'one trusted client CA file should be specified')
2072 if not os.path.isfile(self.options.ssl_client_ca[0]):
2073 raise testserver_base.OptionError(
2074 'specified trusted client CA file not found: ' +
2075 self.options.ssl_client_ca[0] + ' exiting...')
2076 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2077 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002078 print 'WebSocket server started on %s://%s:%d...' % \
2079 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002080 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002081 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002082 elif self.options.server_type == SERVER_TCP_ECHO:
2083 # Used for generating the key (randomly) that encodes the "echo request"
2084 # message.
2085 random.seed()
2086 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002087 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002088 server_data['port'] = server.server_port
2089 elif self.options.server_type == SERVER_UDP_ECHO:
2090 # Used for generating the key (randomly) that encodes the "echo request"
2091 # message.
2092 random.seed()
2093 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002094 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002095 server_data['port'] = server.server_port
2096 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2097 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002098 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002099 server_data['port'] = server.server_port
2100 elif self.options.server_type == SERVER_FTP:
2101 my_data_dir = self.__make_data_dir()
2102
2103 # Instantiate a dummy authorizer for managing 'virtual' users
2104 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2105
2106 # Define a new user having full r/w permissions and a read-only
2107 # anonymous user
2108 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2109
2110 authorizer.add_anonymous(my_data_dir)
2111
2112 # Instantiate FTP handler class
2113 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2114 ftp_handler.authorizer = authorizer
2115
2116 # Define a customized banner (string returned when client connects)
2117 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2118 pyftpdlib.ftpserver.__ver__)
2119
2120 # Instantiate FTP server class and listen to address:port
2121 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2122 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002123 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002124 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002125 raise testserver_base.OptionError('unknown server type' +
2126 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002127
mattm@chromium.org830a3712012-11-07 23:00:07 +00002128 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002129
mattm@chromium.org830a3712012-11-07 23:00:07 +00002130 def run_server(self):
2131 if self.__ocsp_server:
2132 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002133
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002135
mattm@chromium.org830a3712012-11-07 23:00:07 +00002136 if self.__ocsp_server:
2137 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002138
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 def add_options(self):
2140 testserver_base.TestServerRunner.add_options(self)
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002141 self.option_parser.add_option('--disable-session-cache',
2142 action='store_true',
2143 dest='disable_session_cache',
2144 help='tells the server to disable the'
2145 'TLS session cache.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002146 self.option_parser.add_option('-f', '--ftp', action='store_const',
2147 const=SERVER_FTP, default=SERVER_HTTP,
2148 dest='server_type',
2149 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002150 self.option_parser.add_option('--tcp-echo', action='store_const',
2151 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2152 dest='server_type',
2153 help='start up a tcp echo server.')
2154 self.option_parser.add_option('--udp-echo', action='store_const',
2155 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2156 dest='server_type',
2157 help='start up a udp echo server.')
2158 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2159 const=SERVER_BASIC_AUTH_PROXY,
2160 default=SERVER_HTTP, dest='server_type',
2161 help='start up a proxy server which requires '
2162 'basic authentication.')
2163 self.option_parser.add_option('--websocket', action='store_const',
2164 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2165 dest='server_type',
2166 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002167 self.option_parser.add_option('--https', action='store_true',
2168 dest='https', help='Specify that https '
2169 'should be used.')
2170 self.option_parser.add_option('--cert-and-key-file',
2171 dest='cert_and_key_file', help='specify the '
2172 'path to the file containing the certificate '
2173 'and private key for the server in PEM '
2174 'format')
2175 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2176 help='The type of OCSP response generated '
2177 'for the automatically generated '
2178 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002179 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2180 default=0, type=int,
2181 help='If non-zero then the generated '
2182 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002183 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2184 default='0', type='int',
2185 help='If nonzero, certain TLS connections '
2186 'will be aborted in order to test version '
2187 'fallback. 1 means all TLS versions will be '
2188 'aborted. 2 means TLS 1.1 or higher will be '
2189 'aborted. 3 means TLS 1.2 or higher will be '
2190 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002191 self.option_parser.add_option('--tls-intolerance-type',
2192 dest='tls_intolerance_type',
2193 default="alert",
2194 help='Controls how the server reacts to a '
2195 'TLS version it is intolerant to. Valid '
2196 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002197 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2198 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002199 default='',
2200 help='Base64 encoded SCT list. If set, '
2201 'server will respond with a '
2202 'signed_certificate_timestamp TLS extension '
2203 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002204 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2205 default=False, const=True,
2206 action='store_const',
2207 help='If given, TLS_FALLBACK_SCSV support '
2208 'will be enabled. This causes the server to '
2209 'reject fallback connections from compatible '
2210 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002211 self.option_parser.add_option('--staple-ocsp-response',
2212 dest='staple_ocsp_response',
2213 default=False, action='store_true',
2214 help='If set, server will staple the OCSP '
2215 'response whenever OCSP is on and the client '
2216 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002217 self.option_parser.add_option('--https-record-resume',
2218 dest='record_resume', const=True,
2219 default=False, action='store_const',
2220 help='Record resumption cache events rather '
2221 'than resuming as normal. Allows the use of '
2222 'the /ssl-session-cache request')
2223 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2224 help='Require SSL client auth on every '
2225 'connection.')
2226 self.option_parser.add_option('--ssl-client-ca', action='append',
2227 default=[], help='Specify that the client '
2228 'certificate request should include the CA '
2229 'named in the subject of the DER-encoded '
2230 'certificate contained in the specified '
2231 'file. This option may appear multiple '
2232 'times, indicating multiple CA names should '
2233 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002234 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2235 default=[], help='Specify that the client '
2236 'certificate request should include the '
2237 'specified certificate_type value. This '
2238 'option may appear multiple times, '
2239 'indicating multiple values should be send '
2240 'in the request. Valid values are '
2241 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2242 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002243 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2244 help='Specify the bulk encryption '
2245 'algorithm(s) that will be accepted by the '
2246 'SSL server. Valid values are "aes256", '
2247 '"aes128", "3des", "rc4". If omitted, all '
2248 'algorithms will be used. This option may '
2249 'appear multiple times, indicating '
2250 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002251 self.option_parser.add_option('--ssl-key-exchange', action='append',
2252 help='Specify the key exchange algorithm(s)'
2253 'that will be accepted by the SSL server. '
2254 'Valid values are "rsa", "dhe_rsa". If '
2255 'omitted, all algorithms will be used. This '
2256 'option may appear multiple times, '
2257 'indicating multiple algorithms should be '
2258 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002259 # TODO(davidben): Add ALPN support to tlslite.
2260 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2261 default=False, const=True,
2262 action='store_const',
2263 help='Enable server support for the NPN '
2264 'extension. The server will advertise '
2265 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002266 self.option_parser.add_option('--file-root-url', default='/files/',
2267 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002268 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2269 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2270 dest='ws_basic_auth',
2271 help='Enable basic-auth for WebSocket')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002272
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002273
initial.commit94958cf2008-07-26 22:42:52 +00002274if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002275 sys.exit(ServerRunner().main())