blob: 0592b4a151993640a3177a46a8889185cf682315 [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
hirono2838c572015-01-21 12:18:11 -0800677 _, _, _, _, query, _ = urlparse.urlparse(self.path)
678 query_params = cgi.parse_qs(query, True)
679 if 'status' in query_params:
680 self.send_response(int(query_params['status'][0]))
681 else:
682 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000683 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000684 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000685 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000686 return True
687
688 def EchoTitleHandler(self):
689 """This handler is like Echo, but sets the page title to the request."""
690
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000691 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000692 return False
693
694 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000695 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000696 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000697 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000698 self.wfile.write('<html><head><title>')
699 self.wfile.write(request)
700 self.wfile.write('</title></head></html>')
701 return True
702
703 def EchoAllHandler(self):
704 """This handler yields a (more) human-readable page listing information
705 about the request header & contents."""
706
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000707 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000708 return False
709
710 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000711 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000712 self.end_headers()
713 self.wfile.write('<html><head><style>'
714 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
715 '</style></head><body>'
716 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000717 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000718 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000719
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000720 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000721 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000722 params = cgi.parse_qs(qs, keep_blank_values=1)
723
724 for param in params:
725 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000726
727 self.wfile.write('</pre>')
728
729 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
730
731 self.wfile.write('</body></html>')
732 return True
733
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000734 def EchoMultipartPostHandler(self):
735 """This handler echoes received multipart post data as json format."""
736
737 if not (self._ShouldHandleRequest("/echomultipartpost") or
738 self._ShouldHandleRequest("/searchbyimage")):
739 return False
740
741 content_type, parameters = cgi.parse_header(
742 self.headers.getheader('content-type'))
743 if content_type == 'multipart/form-data':
744 post_multipart = cgi.parse_multipart(self.rfile, parameters)
745 elif content_type == 'application/x-www-form-urlencoded':
746 raise Exception('POST by application/x-www-form-urlencoded is '
747 'not implemented.')
748 else:
749 post_multipart = {}
750
751 # Since the data can be binary, we encode them by base64.
752 post_multipart_base64_encoded = {}
753 for field, values in post_multipart.items():
754 post_multipart_base64_encoded[field] = [base64.b64encode(value)
755 for value in values]
756
757 result = {'POST_multipart' : post_multipart_base64_encoded}
758
759 self.send_response(200)
760 self.send_header("Content-type", "text/plain")
761 self.end_headers()
762 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
763 return True
764
initial.commit94958cf2008-07-26 22:42:52 +0000765 def DownloadHandler(self):
766 """This handler sends a downloadable file with or without reporting
767 the size (6K)."""
768
769 if self.path.startswith("/download-unknown-size"):
770 send_length = False
771 elif self.path.startswith("/download-known-size"):
772 send_length = True
773 else:
774 return False
775
776 #
777 # The test which uses this functionality is attempting to send
778 # small chunks of data to the client. Use a fairly large buffer
779 # so that we'll fill chrome's IO buffer enough to force it to
780 # actually write the data.
781 # See also the comments in the client-side of this test in
782 # download_uitest.cc
783 #
784 size_chunk1 = 35*1024
785 size_chunk2 = 10*1024
786
787 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000788 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000789 self.send_header('Cache-Control', 'max-age=0')
790 if send_length:
791 self.send_header('Content-Length', size_chunk1 + size_chunk2)
792 self.end_headers()
793
794 # First chunk of data:
795 self.wfile.write("*" * size_chunk1)
796 self.wfile.flush()
797
798 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000799 self.server.wait_for_download = True
800 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000801 self.server.handle_request()
802
803 # Second chunk of data:
804 self.wfile.write("*" * size_chunk2)
805 return True
806
807 def DownloadFinishHandler(self):
808 """This handler just tells the server to finish the current download."""
809
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000810 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000811 return False
812
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000813 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000814 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000815 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000816 self.send_header('Cache-Control', 'max-age=0')
817 self.end_headers()
818 return True
819
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000820 def _ReplaceFileData(self, data, query_parameters):
821 """Replaces matching substrings in a file.
822
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000823 If the 'replace_text' URL query parameter is present, it is expected to be
824 of the form old_text:new_text, which indicates that any old_text strings in
825 the file are replaced with new_text. Multiple 'replace_text' parameters may
826 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000827
828 If the parameters are not present, |data| is returned.
829 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000830
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000831 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000832 replace_text_values = query_dict.get('replace_text', [])
833 for replace_text_value in replace_text_values:
834 replace_text_args = replace_text_value.split(':')
835 if len(replace_text_args) != 2:
836 raise ValueError(
837 'replace_text must be of form old_text:new_text. Actual value: %s' %
838 replace_text_value)
839 old_text_b64, new_text_b64 = replace_text_args
840 old_text = base64.urlsafe_b64decode(old_text_b64)
841 new_text = base64.urlsafe_b64decode(new_text_b64)
842 data = data.replace(old_text, new_text)
843 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000844
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000845 def ZipFileHandler(self):
846 """This handler sends the contents of the requested file in compressed form.
847 Can pass in a parameter that specifies that the content length be
848 C - the compressed size (OK),
849 U - the uncompressed size (Non-standard, but handled),
850 S - less than compressed (OK because we keep going),
851 M - larger than compressed but less than uncompressed (an error),
852 L - larger than uncompressed (an error)
853 Example: compressedfiles/Picture_1.doc?C
854 """
855
856 prefix = "/compressedfiles/"
857 if not self.path.startswith(prefix):
858 return False
859
860 # Consume a request body if present.
861 if self.command == 'POST' or self.command == 'PUT' :
862 self.ReadRequestBody()
863
864 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
865
866 if not query in ('C', 'U', 'S', 'M', 'L'):
867 return False
868
869 sub_path = url_path[len(prefix):]
870 entries = sub_path.split('/')
871 file_path = os.path.join(self.server.data_dir, *entries)
872 if os.path.isdir(file_path):
873 file_path = os.path.join(file_path, 'index.html')
874
875 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000876 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000877 self.send_error(404)
878 return True
879
880 f = open(file_path, "rb")
881 data = f.read()
882 uncompressed_len = len(data)
883 f.close()
884
885 # Compress the data.
886 data = zlib.compress(data)
887 compressed_len = len(data)
888
889 content_length = compressed_len
890 if query == 'U':
891 content_length = uncompressed_len
892 elif query == 'S':
893 content_length = compressed_len / 2
894 elif query == 'M':
895 content_length = (compressed_len + uncompressed_len) / 2
896 elif query == 'L':
897 content_length = compressed_len + uncompressed_len
898
899 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000900 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000901 self.send_header('Content-encoding', 'deflate')
902 self.send_header('Connection', 'close')
903 self.send_header('Content-Length', content_length)
904 self.send_header('ETag', '\'' + file_path + '\'')
905 self.end_headers()
906
907 self.wfile.write(data)
908
909 return True
910
initial.commit94958cf2008-07-26 22:42:52 +0000911 def FileHandler(self):
912 """This handler sends the contents of the requested file. Wow, it's like
913 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000914
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000915 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000916 if not self.path.startswith(prefix):
917 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000918 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000919
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000920 def PostOnlyFileHandler(self):
921 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000922
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000923 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000924 if not self.path.startswith(prefix):
925 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000926 return self._FileHandlerHelper(prefix)
927
928 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000929 request_body = ''
930 if self.command == 'POST' or self.command == 'PUT':
931 # Consume a request body if present.
932 request_body = self.ReadRequestBody()
933
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000934 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000935 query_dict = cgi.parse_qs(query)
936
937 expected_body = query_dict.get('expected_body', [])
938 if expected_body and request_body not in expected_body:
939 self.send_response(404)
940 self.end_headers()
941 self.wfile.write('')
942 return True
943
944 expected_headers = query_dict.get('expected_headers', [])
945 for expected_header in expected_headers:
946 header_name, expected_value = expected_header.split(':')
947 if self.headers.getheader(header_name) != expected_value:
948 self.send_response(404)
949 self.end_headers()
950 self.wfile.write('')
951 return True
952
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000953 sub_path = url_path[len(prefix):]
954 entries = sub_path.split('/')
955 file_path = os.path.join(self.server.data_dir, *entries)
956 if os.path.isdir(file_path):
957 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000958
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000959 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000960 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000961 self.send_error(404)
962 return True
963
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000964 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000965 data = f.read()
966 f.close()
967
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000968 data = self._ReplaceFileData(data, query)
969
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000970 old_protocol_version = self.protocol_version
971
initial.commit94958cf2008-07-26 22:42:52 +0000972 # If file.mock-http-headers exists, it contains the headers we
973 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000974 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000975 if os.path.isfile(headers_path):
976 f = open(headers_path, "r")
977
978 # "HTTP/1.1 200 OK"
979 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000980 http_major, http_minor, status_code = re.findall(
981 'HTTP/(\d+).(\d+) (\d+)', response)[0]
982 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000983 self.send_response(int(status_code))
984
985 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000986 header_values = re.findall('(\S+):\s*(.*)', line)
987 if len(header_values) > 0:
988 # "name: value"
989 name, value = header_values[0]
990 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000991 f.close()
992 else:
993 # Could be more generic once we support mime-type sniffing, but for
994 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000995
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000996 range_header = self.headers.get('Range')
997 if range_header and range_header.startswith('bytes='):
998 # Note this doesn't handle all valid byte range_header values (i.e.
999 # left open ended ones), just enough for what we needed so far.
1000 range_header = range_header[6:].split('-')
1001 start = int(range_header[0])
1002 if range_header[1]:
1003 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001004 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001005 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001006
1007 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001008 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1009 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001010 self.send_header('Content-Range', content_range)
1011 data = data[start: end + 1]
1012 else:
1013 self.send_response(200)
1014
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001015 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001016 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001017 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001018 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001019 self.end_headers()
1020
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001021 if (self.command != 'HEAD'):
1022 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001023
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001024 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001025 return True
1026
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001027 def SetCookieHandler(self):
1028 """This handler just sets a cookie, for testing cookie handling."""
1029
1030 if not self._ShouldHandleRequest("/set-cookie"):
1031 return False
1032
1033 query_char = self.path.find('?')
1034 if query_char != -1:
1035 cookie_values = self.path[query_char + 1:].split('&')
1036 else:
1037 cookie_values = ("",)
1038 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001039 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001040 for cookie_value in cookie_values:
1041 self.send_header('Set-Cookie', '%s' % cookie_value)
1042 self.end_headers()
1043 for cookie_value in cookie_values:
1044 self.wfile.write('%s' % cookie_value)
1045 return True
1046
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001047 def SetManyCookiesHandler(self):
1048 """This handler just sets a given number of cookies, for testing handling
1049 of large numbers of cookies."""
1050
1051 if not self._ShouldHandleRequest("/set-many-cookies"):
1052 return False
1053
1054 query_char = self.path.find('?')
1055 if query_char != -1:
1056 num_cookies = int(self.path[query_char + 1:])
1057 else:
1058 num_cookies = 0
1059 self.send_response(200)
1060 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001061 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001062 self.send_header('Set-Cookie', 'a=')
1063 self.end_headers()
1064 self.wfile.write('%d cookies were sent' % num_cookies)
1065 return True
1066
mattm@chromium.org983fc462012-06-30 00:52:08 +00001067 def ExpectAndSetCookieHandler(self):
1068 """Expects some cookies to be sent, and if they are, sets more cookies.
1069
1070 The expect parameter specifies a required cookie. May be specified multiple
1071 times.
1072 The set parameter specifies a cookie to set if all required cookies are
1073 preset. May be specified multiple times.
1074 The data parameter specifies the response body data to be returned."""
1075
1076 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1077 return False
1078
1079 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1080 query_dict = cgi.parse_qs(query)
1081 cookies = set()
1082 if 'Cookie' in self.headers:
1083 cookie_header = self.headers.getheader('Cookie')
1084 cookies.update([s.strip() for s in cookie_header.split(';')])
1085 got_all_expected_cookies = True
1086 for expected_cookie in query_dict.get('expect', []):
1087 if expected_cookie not in cookies:
1088 got_all_expected_cookies = False
1089 self.send_response(200)
1090 self.send_header('Content-Type', 'text/html')
1091 if got_all_expected_cookies:
1092 for cookie_value in query_dict.get('set', []):
1093 self.send_header('Set-Cookie', '%s' % cookie_value)
1094 self.end_headers()
1095 for data_value in query_dict.get('data', []):
1096 self.wfile.write(data_value)
1097 return True
1098
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001099 def SetHeaderHandler(self):
1100 """This handler sets a response header. Parameters are in the
1101 key%3A%20value&key2%3A%20value2 format."""
1102
1103 if not self._ShouldHandleRequest("/set-header"):
1104 return False
1105
1106 query_char = self.path.find('?')
1107 if query_char != -1:
1108 headers_values = self.path[query_char + 1:].split('&')
1109 else:
1110 headers_values = ("",)
1111 self.send_response(200)
1112 self.send_header('Content-Type', 'text/html')
1113 for header_value in headers_values:
1114 header_value = urllib.unquote(header_value)
1115 (key, value) = header_value.split(': ', 1)
1116 self.send_header(key, value)
1117 self.end_headers()
1118 for header_value in headers_values:
1119 self.wfile.write('%s' % header_value)
1120 return True
1121
initial.commit94958cf2008-07-26 22:42:52 +00001122 def AuthBasicHandler(self):
1123 """This handler tests 'Basic' authentication. It just sends a page with
1124 title 'user/pass' if you succeed."""
1125
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001126 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001127 return False
1128
1129 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001130 expected_password = 'secret'
1131 realm = 'testrealm'
1132 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001133
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001134 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1135 query_params = cgi.parse_qs(query, True)
1136 if 'set-cookie-if-challenged' in query_params:
1137 set_cookie_if_challenged = True
1138 if 'password' in query_params:
1139 expected_password = query_params['password'][0]
1140 if 'realm' in query_params:
1141 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001142
initial.commit94958cf2008-07-26 22:42:52 +00001143 auth = self.headers.getheader('authorization')
1144 try:
1145 if not auth:
1146 raise Exception('no auth')
1147 b64str = re.findall(r'Basic (\S+)', auth)[0]
1148 userpass = base64.b64decode(b64str)
1149 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001150 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001151 raise Exception('wrong password')
1152 except Exception, e:
1153 # Authentication failed.
1154 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001155 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001156 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001157 if set_cookie_if_challenged:
1158 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001159 self.end_headers()
1160 self.wfile.write('<html><head>')
1161 self.wfile.write('<title>Denied: %s</title>' % e)
1162 self.wfile.write('</head><body>')
1163 self.wfile.write('auth=%s<p>' % auth)
1164 self.wfile.write('b64str=%s<p>' % b64str)
1165 self.wfile.write('username: %s<p>' % username)
1166 self.wfile.write('userpass: %s<p>' % userpass)
1167 self.wfile.write('password: %s<p>' % password)
1168 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1169 self.wfile.write('</body></html>')
1170 return True
1171
1172 # Authentication successful. (Return a cachable response to allow for
1173 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001174 old_protocol_version = self.protocol_version
1175 self.protocol_version = "HTTP/1.1"
1176
initial.commit94958cf2008-07-26 22:42:52 +00001177 if_none_match = self.headers.getheader('if-none-match')
1178 if if_none_match == "abc":
1179 self.send_response(304)
1180 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001181 elif url_path.endswith(".gif"):
1182 # Using chrome/test/data/google/logo.gif as the test image
1183 test_image_path = ['google', 'logo.gif']
1184 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1185 if not os.path.isfile(gif_path):
1186 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001187 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001188 return True
1189
1190 f = open(gif_path, "rb")
1191 data = f.read()
1192 f.close()
1193
1194 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001195 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001196 self.send_header('Cache-control', 'max-age=60000')
1197 self.send_header('Etag', 'abc')
1198 self.end_headers()
1199 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001200 else:
1201 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001202 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001203 self.send_header('Cache-control', 'max-age=60000')
1204 self.send_header('Etag', 'abc')
1205 self.end_headers()
1206 self.wfile.write('<html><head>')
1207 self.wfile.write('<title>%s/%s</title>' % (username, password))
1208 self.wfile.write('</head><body>')
1209 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001210 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001211 self.wfile.write('</body></html>')
1212
rvargas@google.com54453b72011-05-19 01:11:11 +00001213 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001214 return True
1215
tonyg@chromium.org75054202010-03-31 22:06:10 +00001216 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001217 """Returns a nonce that's stable per request path for the server's lifetime.
1218 This is a fake implementation. A real implementation would only use a given
1219 nonce a single time (hence the name n-once). However, for the purposes of
1220 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001221
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001222 Args:
1223 force_reset: Iff set, the nonce will be changed. Useful for testing the
1224 "stale" response.
1225 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001226
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001227 if force_reset or not self.server.nonce_time:
1228 self.server.nonce_time = time.time()
1229 return hashlib.md5('privatekey%s%d' %
1230 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001231
1232 def AuthDigestHandler(self):
1233 """This handler tests 'Digest' authentication.
1234
1235 It just sends a page with title 'user/pass' if you succeed.
1236
1237 A stale response is sent iff "stale" is present in the request path.
1238 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001239
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001240 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001241 return False
1242
tonyg@chromium.org75054202010-03-31 22:06:10 +00001243 stale = 'stale' in self.path
1244 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001245 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001246 password = 'secret'
1247 realm = 'testrealm'
1248
1249 auth = self.headers.getheader('authorization')
1250 pairs = {}
1251 try:
1252 if not auth:
1253 raise Exception('no auth')
1254 if not auth.startswith('Digest'):
1255 raise Exception('not digest')
1256 # Pull out all the name="value" pairs as a dictionary.
1257 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1258
1259 # Make sure it's all valid.
1260 if pairs['nonce'] != nonce:
1261 raise Exception('wrong nonce')
1262 if pairs['opaque'] != opaque:
1263 raise Exception('wrong opaque')
1264
1265 # Check the 'response' value and make sure it matches our magic hash.
1266 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001267 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001268 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001269 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001270 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001271 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001272 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1273 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001274 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001275
1276 if pairs['response'] != response:
1277 raise Exception('wrong password')
1278 except Exception, e:
1279 # Authentication failed.
1280 self.send_response(401)
1281 hdr = ('Digest '
1282 'realm="%s", '
1283 'domain="/", '
1284 'qop="auth", '
1285 'algorithm=MD5, '
1286 'nonce="%s", '
1287 'opaque="%s"') % (realm, nonce, opaque)
1288 if stale:
1289 hdr += ', stale="TRUE"'
1290 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001291 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001292 self.end_headers()
1293 self.wfile.write('<html><head>')
1294 self.wfile.write('<title>Denied: %s</title>' % e)
1295 self.wfile.write('</head><body>')
1296 self.wfile.write('auth=%s<p>' % auth)
1297 self.wfile.write('pairs=%s<p>' % pairs)
1298 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1299 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1300 self.wfile.write('</body></html>')
1301 return True
1302
1303 # Authentication successful.
1304 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001305 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001306 self.end_headers()
1307 self.wfile.write('<html><head>')
1308 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1309 self.wfile.write('</head><body>')
1310 self.wfile.write('auth=%s<p>' % auth)
1311 self.wfile.write('pairs=%s<p>' % pairs)
1312 self.wfile.write('</body></html>')
1313
1314 return True
1315
1316 def SlowServerHandler(self):
1317 """Wait for the user suggested time before responding. The syntax is
1318 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001319
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001320 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001321 return False
1322 query_char = self.path.find('?')
1323 wait_sec = 1.0
1324 if query_char >= 0:
1325 try:
1326 wait_sec = int(self.path[query_char + 1:])
1327 except ValueError:
1328 pass
1329 time.sleep(wait_sec)
1330 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001331 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001332 self.end_headers()
1333 self.wfile.write("waited %d seconds" % wait_sec)
1334 return True
1335
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001336 def ChunkedServerHandler(self):
1337 """Send chunked response. Allows to specify chunks parameters:
1338 - waitBeforeHeaders - ms to wait before sending headers
1339 - waitBetweenChunks - ms to wait between chunks
1340 - chunkSize - size of each chunk in bytes
1341 - chunksNumber - number of chunks
1342 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1343 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001344
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001345 if not self._ShouldHandleRequest("/chunked"):
1346 return False
1347 query_char = self.path.find('?')
1348 chunkedSettings = {'waitBeforeHeaders' : 0,
1349 'waitBetweenChunks' : 0,
1350 'chunkSize' : 5,
1351 'chunksNumber' : 5}
1352 if query_char >= 0:
1353 params = self.path[query_char + 1:].split('&')
1354 for param in params:
1355 keyValue = param.split('=')
1356 if len(keyValue) == 2:
1357 try:
1358 chunkedSettings[keyValue[0]] = int(keyValue[1])
1359 except ValueError:
1360 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001361 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001362 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1363 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001364 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001365 self.send_header('Connection', 'close')
1366 self.send_header('Transfer-Encoding', 'chunked')
1367 self.end_headers()
1368 # Chunked encoding: sending all chunks, then final zero-length chunk and
1369 # then final CRLF.
1370 for i in range(0, chunkedSettings['chunksNumber']):
1371 if i > 0:
1372 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1373 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001374 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001375 self.sendChunkHelp('')
1376 return True
1377
initial.commit94958cf2008-07-26 22:42:52 +00001378 def ContentTypeHandler(self):
1379 """Returns a string of html with the given content type. E.g.,
1380 /contenttype?text/css returns an html file with the Content-Type
1381 header set to text/css."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001382
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001383 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +00001384 return False
1385 query_char = self.path.find('?')
1386 content_type = self.path[query_char + 1:].strip()
1387 if not content_type:
1388 content_type = 'text/html'
1389 self.send_response(200)
1390 self.send_header('Content-Type', content_type)
1391 self.end_headers()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001392 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
initial.commit94958cf2008-07-26 22:42:52 +00001393 return True
1394
creis@google.com2f4f6a42011-03-25 19:44:19 +00001395 def NoContentHandler(self):
1396 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001397
creis@google.com2f4f6a42011-03-25 19:44:19 +00001398 if not self._ShouldHandleRequest("/nocontent"):
1399 return False
1400 self.send_response(204)
1401 self.end_headers()
1402 return True
1403
initial.commit94958cf2008-07-26 22:42:52 +00001404 def ServerRedirectHandler(self):
1405 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001406 '/server-redirect?http://foo.bar/asdf' to redirect to
1407 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001408
1409 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001410 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001411 return False
1412
1413 query_char = self.path.find('?')
1414 if query_char < 0 or len(self.path) <= query_char + 1:
1415 self.sendRedirectHelp(test_name)
1416 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001417 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001418
1419 self.send_response(301) # moved permanently
1420 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001421 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001422 self.end_headers()
1423 self.wfile.write('<html><head>')
1424 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1425
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001426 return True
initial.commit94958cf2008-07-26 22:42:52 +00001427
naskoe7a0d0d2014-09-29 08:53:05 -07001428 def CrossSiteRedirectHandler(self):
1429 """Sends a server redirect to the given site. The syntax is
1430 '/cross-site/hostname/...' to redirect to //hostname/...
1431 It is used to navigate between different Sites, causing
1432 cross-site/cross-process navigations in the browser."""
1433
1434 test_name = "/cross-site"
1435 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001436 return False
1437
1438 params = urllib.unquote(self.path[(len(test_name) + 1):])
1439 slash = params.find('/')
1440 if slash < 0:
1441 self.sendRedirectHelp(test_name)
1442 return True
1443
1444 host = params[:slash]
1445 path = params[(slash+1):]
1446 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1447
1448 self.send_response(301) # moved permanently
1449 self.send_header('Location', dest)
1450 self.send_header('Content-Type', 'text/html')
1451 self.end_headers()
1452 self.wfile.write('<html><head>')
1453 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1454
1455 return True
1456
initial.commit94958cf2008-07-26 22:42:52 +00001457 def ClientRedirectHandler(self):
1458 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001459 '/client-redirect?http://foo.bar/asdf' to redirect to
1460 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001461
1462 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001463 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001464 return False
1465
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001466 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001467 if query_char < 0 or len(self.path) <= query_char + 1:
1468 self.sendRedirectHelp(test_name)
1469 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001470 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001471
1472 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001473 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001474 self.end_headers()
1475 self.wfile.write('<html><head>')
1476 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1477 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1478
1479 return True
1480
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001481 def GetSSLSessionCacheHandler(self):
1482 """Send a reply containing a log of the session cache operations."""
1483
1484 if not self._ShouldHandleRequest('/ssl-session-cache'):
1485 return False
1486
1487 self.send_response(200)
1488 self.send_header('Content-Type', 'text/plain')
1489 self.end_headers()
1490 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001491 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001492 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001493 self.wfile.write('Pass --https-record-resume in order to use' +
1494 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001495 return True
1496
1497 for (action, sessionID) in log:
1498 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001499 return True
1500
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001501 def SSLManySmallRecords(self):
1502 """Sends a reply consisting of a variety of small writes. These will be
1503 translated into a series of small SSL records when used over an HTTPS
1504 server."""
1505
1506 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1507 return False
1508
1509 self.send_response(200)
1510 self.send_header('Content-Type', 'text/plain')
1511 self.end_headers()
1512
1513 # Write ~26K of data, in 1350 byte chunks
1514 for i in xrange(20):
1515 self.wfile.write('*' * 1350)
1516 self.wfile.flush()
1517 return True
1518
agl@chromium.org04700be2013-03-02 18:40:41 +00001519 def GetChannelID(self):
1520 """Send a reply containing the hashed ChannelID that the client provided."""
1521
1522 if not self._ShouldHandleRequest('/channel-id'):
1523 return False
1524
1525 self.send_response(200)
1526 self.send_header('Content-Type', 'text/plain')
1527 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001528 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001529 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1530 return True
1531
davidben599e7e72014-09-03 16:19:09 -07001532 def ClientCipherListHandler(self):
1533 """Send a reply containing the cipher suite list that the client
1534 provided. Each cipher suite value is serialized in decimal, followed by a
1535 newline."""
1536
1537 if not self._ShouldHandleRequest('/client-cipher-list'):
1538 return False
1539
1540 self.send_response(200)
1541 self.send_header('Content-Type', 'text/plain')
1542 self.end_headers()
1543
davidben11682512014-10-06 21:09:11 -07001544 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1545 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001546 return True
1547
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001548 def CloseSocketHandler(self):
1549 """Closes the socket without sending anything."""
1550
1551 if not self._ShouldHandleRequest('/close-socket'):
1552 return False
1553
1554 self.wfile.close()
1555 return True
1556
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001557 def RangeResetHandler(self):
1558 """Send data broken up by connection resets every N (default 4K) bytes.
1559 Support range requests. If the data requested doesn't straddle a reset
1560 boundary, it will all be sent. Used for testing resuming downloads."""
1561
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001562 def DataForRange(start, end):
1563 """Data to be provided for a particular range of bytes."""
1564 # Offset and scale to avoid too obvious (and hence potentially
1565 # collidable) data.
1566 return ''.join([chr(y % 256)
1567 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1568
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001569 if not self._ShouldHandleRequest('/rangereset'):
1570 return False
1571
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001572 # HTTP/1.1 is required for ETag and range support.
1573 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001574 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1575
1576 # Defaults
1577 size = 8000
1578 # Note that the rst is sent just before sending the rst_boundary byte.
1579 rst_boundary = 4000
1580 respond_to_range = True
1581 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001582 rst_limit = -1
1583 token = 'DEFAULT'
1584 fail_precondition = 0
1585 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001586
1587 # Parse the query
1588 qdict = urlparse.parse_qs(query, True)
1589 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001590 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001591 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001592 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001593 if 'token' in qdict:
1594 # Identifying token for stateful tests.
1595 token = qdict['token'][0]
1596 if 'rst_limit' in qdict:
1597 # Max number of rsts for a given token.
1598 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001599 if 'bounce_range' in qdict:
1600 respond_to_range = False
1601 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001602 # Note that hold_for_signal will not work with null range requests;
1603 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001604 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001605 if 'no_verifiers' in qdict:
1606 send_verifiers = False
1607 if 'fail_precondition' in qdict:
1608 fail_precondition = int(qdict['fail_precondition'][0])
1609
1610 # Record already set information, or set it.
1611 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1612 if rst_limit != 0:
1613 TestPageHandler.rst_limits[token] -= 1
1614 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1615 token, fail_precondition)
1616 if fail_precondition != 0:
1617 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001618
1619 first_byte = 0
1620 last_byte = size - 1
1621
1622 # Does that define what we want to return, or do we need to apply
1623 # a range?
1624 range_response = False
1625 range_header = self.headers.getheader('range')
1626 if range_header and respond_to_range:
1627 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1628 if mo.group(1):
1629 first_byte = int(mo.group(1))
1630 if mo.group(2):
1631 last_byte = int(mo.group(2))
1632 if last_byte > size - 1:
1633 last_byte = size - 1
1634 range_response = True
1635 if last_byte < first_byte:
1636 return False
1637
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001638 if (fail_precondition and
1639 (self.headers.getheader('If-Modified-Since') or
1640 self.headers.getheader('If-Match'))):
1641 self.send_response(412)
1642 self.end_headers()
1643 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001644
1645 if range_response:
1646 self.send_response(206)
1647 self.send_header('Content-Range',
1648 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1649 else:
1650 self.send_response(200)
1651 self.send_header('Content-Type', 'application/octet-stream')
1652 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001653 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001654 # If fail_precondition is non-zero, then the ETag for each request will be
1655 # different.
1656 etag = "%s%d" % (token, fail_precondition)
1657 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001658 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001659 self.end_headers()
1660
1661 if hold_for_signal:
1662 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1663 # a single byte, the self.server.handle_request() below hangs
1664 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001665 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001666 first_byte = first_byte + 1
1667 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001668 self.server.wait_for_download = True
1669 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001670 self.server.handle_request()
1671
1672 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001673 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001674 # No RST has been requested in this range, so we don't need to
1675 # do anything fancy; just write the data and let the python
1676 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001677 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001678 self.wfile.flush()
1679 return True
1680
1681 # We're resetting the connection part way in; go to the RST
1682 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001683 # Because socket semantics do not guarantee that all the data will be
1684 # sent when using the linger semantics to hard close a socket,
1685 # we send the data and then wait for our peer to release us
1686 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001687 data = DataForRange(first_byte, possible_rst)
1688 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001689 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001690 self.server.wait_for_download = True
1691 while self.server.wait_for_download:
1692 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001693 l_onoff = 1 # Linger is active.
1694 l_linger = 0 # Seconds to linger for.
1695 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1696 struct.pack('ii', l_onoff, l_linger))
1697
1698 # Close all duplicates of the underlying socket to force the RST.
1699 self.wfile.close()
1700 self.rfile.close()
1701 self.connection.close()
1702
1703 return True
1704
initial.commit94958cf2008-07-26 22:42:52 +00001705 def DefaultResponseHandler(self):
1706 """This is the catch-all response handler for requests that aren't handled
1707 by one of the special handlers above.
1708 Note that we specify the content-length as without it the https connection
1709 is not closed properly (and the browser keeps expecting data)."""
1710
1711 contents = "Default response given for path: " + self.path
1712 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001713 self.send_header('Content-Type', 'text/html')
1714 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001715 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001716 if (self.command != 'HEAD'):
1717 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001718 return True
1719
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001720 def RedirectConnectHandler(self):
1721 """Sends a redirect to the CONNECT request for www.redirect.com. This
1722 response is not specified by the RFC, so the browser should not follow
1723 the redirect."""
1724
1725 if (self.path.find("www.redirect.com") < 0):
1726 return False
1727
1728 dest = "http://www.destination.com/foo.js"
1729
1730 self.send_response(302) # moved temporarily
1731 self.send_header('Location', dest)
1732 self.send_header('Connection', 'close')
1733 self.end_headers()
1734 return True
1735
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001736 def ServerAuthConnectHandler(self):
1737 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1738 response doesn't make sense because the proxy server cannot request
1739 server authentication."""
1740
1741 if (self.path.find("www.server-auth.com") < 0):
1742 return False
1743
1744 challenge = 'Basic realm="WallyWorld"'
1745
1746 self.send_response(401) # unauthorized
1747 self.send_header('WWW-Authenticate', challenge)
1748 self.send_header('Connection', 'close')
1749 self.end_headers()
1750 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001751
1752 def DefaultConnectResponseHandler(self):
1753 """This is the catch-all response handler for CONNECT requests that aren't
1754 handled by one of the special handlers above. Real Web servers respond
1755 with 400 to CONNECT requests."""
1756
1757 contents = "Your client has issued a malformed or illegal request."
1758 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001759 self.send_header('Content-Type', 'text/html')
1760 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001761 self.end_headers()
1762 self.wfile.write(contents)
1763 return True
1764
initial.commit94958cf2008-07-26 22:42:52 +00001765 # called by the redirect handling function when there is no parameter
1766 def sendRedirectHelp(self, redirect_name):
1767 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001768 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001769 self.end_headers()
1770 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1771 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1772 self.wfile.write('</body></html>')
1773
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001774 # called by chunked handling function
1775 def sendChunkHelp(self, chunk):
1776 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1777 self.wfile.write('%X\r\n' % len(chunk))
1778 self.wfile.write(chunk)
1779 self.wfile.write('\r\n')
1780
akalin@chromium.org154bb132010-11-12 02:20:27 +00001781
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001782class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001783 def __init__(self, request, client_address, socket_server):
1784 handlers = [self.OCSPResponse]
1785 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001786 testserver_base.BasePageHandler.__init__(self, request, client_address,
1787 socket_server, [], handlers, [],
1788 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001789
1790 def OCSPResponse(self):
1791 self.send_response(200)
1792 self.send_header('Content-Type', 'application/ocsp-response')
1793 self.send_header('Content-Length', str(len(self.ocsp_response)))
1794 self.end_headers()
1795
1796 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001797
mattm@chromium.org830a3712012-11-07 23:00:07 +00001798
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001799class TCPEchoHandler(SocketServer.BaseRequestHandler):
1800 """The RequestHandler class for TCP echo server.
1801
1802 It is instantiated once per connection to the server, and overrides the
1803 handle() method to implement communication to the client.
1804 """
1805
1806 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001807 """Handles the request from the client and constructs a response."""
1808
1809 data = self.request.recv(65536).strip()
1810 # Verify the "echo request" message received from the client. Send back
1811 # "echo response" message if "echo request" message is valid.
1812 try:
1813 return_data = echo_message.GetEchoResponseData(data)
1814 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001815 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001816 except ValueError:
1817 return
1818
1819 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001820
1821
1822class UDPEchoHandler(SocketServer.BaseRequestHandler):
1823 """The RequestHandler class for UDP echo server.
1824
1825 It is instantiated once per connection to the server, and overrides the
1826 handle() method to implement communication to the client.
1827 """
1828
1829 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001830 """Handles the request from the client and constructs a response."""
1831
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001832 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001833 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001834 # Verify the "echo request" message received from the client. Send back
1835 # "echo response" message if "echo request" message is valid.
1836 try:
1837 return_data = echo_message.GetEchoResponseData(data)
1838 if not return_data:
1839 return
1840 except ValueError:
1841 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001842 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001843
1844
bashi@chromium.org33233532012-09-08 17:37:24 +00001845class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1846 """A request handler that behaves as a proxy server which requires
1847 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1848 """
1849
1850 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1851
1852 def parse_request(self):
1853 """Overrides parse_request to check credential."""
1854
1855 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1856 return False
1857
1858 auth = self.headers.getheader('Proxy-Authorization')
1859 if auth != self._AUTH_CREDENTIAL:
1860 self.send_response(407)
1861 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1862 self.end_headers()
1863 return False
1864
1865 return True
1866
1867 def _start_read_write(self, sock):
1868 sock.setblocking(0)
1869 self.request.setblocking(0)
1870 rlist = [self.request, sock]
1871 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001872 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001873 if errors:
1874 self.send_response(500)
1875 self.end_headers()
1876 return
1877 for s in ready_sockets:
1878 received = s.recv(1024)
1879 if len(received) == 0:
1880 return
1881 if s == self.request:
1882 other = sock
1883 else:
1884 other = self.request
1885 other.send(received)
1886
1887 def _do_common_method(self):
1888 url = urlparse.urlparse(self.path)
1889 port = url.port
1890 if not port:
1891 if url.scheme == 'http':
1892 port = 80
1893 elif url.scheme == 'https':
1894 port = 443
1895 if not url.hostname or not port:
1896 self.send_response(400)
1897 self.end_headers()
1898 return
1899
1900 if len(url.path) == 0:
1901 path = '/'
1902 else:
1903 path = url.path
1904 if len(url.query) > 0:
1905 path = '%s?%s' % (url.path, url.query)
1906
1907 sock = None
1908 try:
1909 sock = socket.create_connection((url.hostname, port))
1910 sock.send('%s %s %s\r\n' % (
1911 self.command, path, self.protocol_version))
1912 for header in self.headers.headers:
1913 header = header.strip()
1914 if (header.lower().startswith('connection') or
1915 header.lower().startswith('proxy')):
1916 continue
1917 sock.send('%s\r\n' % header)
1918 sock.send('\r\n')
1919 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001920 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001921 self.send_response(500)
1922 self.end_headers()
1923 finally:
1924 if sock is not None:
1925 sock.close()
1926
1927 def do_CONNECT(self):
1928 try:
1929 pos = self.path.rfind(':')
1930 host = self.path[:pos]
1931 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001932 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001933 self.send_response(400)
1934 self.end_headers()
1935
1936 try:
1937 sock = socket.create_connection((host, port))
1938 self.send_response(200, 'Connection established')
1939 self.end_headers()
1940 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001941 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001942 self.send_response(500)
1943 self.end_headers()
1944 finally:
1945 sock.close()
1946
1947 def do_GET(self):
1948 self._do_common_method()
1949
1950 def do_HEAD(self):
1951 self._do_common_method()
1952
1953
mattm@chromium.org830a3712012-11-07 23:00:07 +00001954class ServerRunner(testserver_base.TestServerRunner):
1955 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001956
mattm@chromium.org830a3712012-11-07 23:00:07 +00001957 def __init__(self):
1958 super(ServerRunner, self).__init__()
1959 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001960
mattm@chromium.org830a3712012-11-07 23:00:07 +00001961 def __make_data_dir(self):
1962 if self.options.data_dir:
1963 if not os.path.isdir(self.options.data_dir):
1964 raise testserver_base.OptionError('specified data dir not found: ' +
1965 self.options.data_dir + ' exiting...')
1966 my_data_dir = self.options.data_dir
1967 else:
1968 # Create the default path to our data dir, relative to the exe dir.
1969 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1970 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001971
mattm@chromium.org830a3712012-11-07 23:00:07 +00001972 #TODO(ibrar): Must use Find* funtion defined in google\tools
1973 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001974
mattm@chromium.org830a3712012-11-07 23:00:07 +00001975 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001976
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 def create_server(self, server_data):
1978 port = self.options.port
1979 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001980
mattm@chromium.org830a3712012-11-07 23:00:07 +00001981 if self.options.server_type == SERVER_HTTP:
1982 if self.options.https:
1983 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001984 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001985 if self.options.cert_and_key_file:
1986 if not os.path.isfile(self.options.cert_and_key_file):
1987 raise testserver_base.OptionError(
1988 'specified server cert file not found: ' +
1989 self.options.cert_and_key_file + ' exiting...')
1990 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00001991 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00001992 # generate a new certificate and run an OCSP server for it.
1993 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00001994 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00001995 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001996
mattm@chromium.org830a3712012-11-07 23:00:07 +00001997 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00001998
mattm@chromium.org830a3712012-11-07 23:00:07 +00001999 if self.options.ocsp == 'ok':
2000 ocsp_state = minica.OCSP_STATE_GOOD
2001 elif self.options.ocsp == 'revoked':
2002 ocsp_state = minica.OCSP_STATE_REVOKED
2003 elif self.options.ocsp == 'invalid':
2004 ocsp_state = minica.OCSP_STATE_INVALID
2005 elif self.options.ocsp == 'unauthorized':
2006 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2007 elif self.options.ocsp == 'unknown':
2008 ocsp_state = minica.OCSP_STATE_UNKNOWN
2009 else:
2010 raise testserver_base.OptionError('unknown OCSP status: ' +
2011 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002012
mattm@chromium.org830a3712012-11-07 23:00:07 +00002013 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2014 subject = "127.0.0.1",
2015 ocsp_url = ("http://%s:%d/ocsp" %
2016 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002017 ocsp_state = ocsp_state,
2018 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002019
davidben3e2564a2014-11-07 18:51:00 -08002020 if self.options.ocsp_server_unavailable:
2021 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2022 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2023 else:
2024 self.__ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org830a3712012-11-07 23:00:07 +00002025
2026 for ca_cert in self.options.ssl_client_ca:
2027 if not os.path.isfile(ca_cert):
2028 raise testserver_base.OptionError(
2029 'specified trusted client CA file not found: ' + ca_cert +
2030 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002031
2032 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002033 if self.options.staple_ocsp_response:
2034 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002035
mattm@chromium.org830a3712012-11-07 23:00:07 +00002036 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2037 self.options.ssl_client_auth,
2038 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002039 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002040 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002041 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002042 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002043 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002044 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002045 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002046 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002047 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002048 self.options.fallback_scsv,
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002049 stapled_ocsp_response,
2050 self.options.disable_session_cache)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002051 print 'HTTPS server started on https://%s:%d...' % \
2052 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 else:
2054 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002055 print 'HTTP server started on http://%s:%d...' % \
2056 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002057
2058 server.data_dir = self.__make_data_dir()
2059 server.file_root_url = self.options.file_root_url
2060 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002061 elif self.options.server_type == SERVER_WEBSOCKET:
2062 # Launch pywebsocket via WebSocketServer.
2063 logger = logging.getLogger()
2064 logger.addHandler(logging.StreamHandler())
2065 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2066 # is required to work correctly. It should be fixed from pywebsocket side.
2067 os.chdir(self.__make_data_dir())
2068 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002069 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002070 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002071 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002072 websocket_options.use_tls = True
2073 websocket_options.private_key = self.options.cert_and_key_file
2074 websocket_options.certificate = self.options.cert_and_key_file
2075 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002076 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002077 websocket_options.tls_client_auth = True
2078 if len(self.options.ssl_client_ca) != 1:
2079 raise testserver_base.OptionError(
2080 'one trusted client CA file should be specified')
2081 if not os.path.isfile(self.options.ssl_client_ca[0]):
2082 raise testserver_base.OptionError(
2083 'specified trusted client CA file not found: ' +
2084 self.options.ssl_client_ca[0] + ' exiting...')
2085 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
2086 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002087 print 'WebSocket server started on %s://%s:%d...' % \
2088 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002089 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002090 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002091 elif self.options.server_type == SERVER_TCP_ECHO:
2092 # Used for generating the key (randomly) that encodes the "echo request"
2093 # message.
2094 random.seed()
2095 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002096 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002097 server_data['port'] = server.server_port
2098 elif self.options.server_type == SERVER_UDP_ECHO:
2099 # Used for generating the key (randomly) that encodes the "echo request"
2100 # message.
2101 random.seed()
2102 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002103 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002104 server_data['port'] = server.server_port
2105 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2106 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002107 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002108 server_data['port'] = server.server_port
2109 elif self.options.server_type == SERVER_FTP:
2110 my_data_dir = self.__make_data_dir()
2111
2112 # Instantiate a dummy authorizer for managing 'virtual' users
2113 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2114
2115 # Define a new user having full r/w permissions and a read-only
2116 # anonymous user
2117 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2118
2119 authorizer.add_anonymous(my_data_dir)
2120
2121 # Instantiate FTP handler class
2122 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2123 ftp_handler.authorizer = authorizer
2124
2125 # Define a customized banner (string returned when client connects)
2126 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2127 pyftpdlib.ftpserver.__ver__)
2128
2129 # Instantiate FTP server class and listen to address:port
2130 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2131 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002132 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002133 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002134 raise testserver_base.OptionError('unknown server type' +
2135 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002136
mattm@chromium.org830a3712012-11-07 23:00:07 +00002137 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002138
mattm@chromium.org830a3712012-11-07 23:00:07 +00002139 def run_server(self):
2140 if self.__ocsp_server:
2141 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002142
mattm@chromium.org830a3712012-11-07 23:00:07 +00002143 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002144
mattm@chromium.org830a3712012-11-07 23:00:07 +00002145 if self.__ocsp_server:
2146 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002147
mattm@chromium.org830a3712012-11-07 23:00:07 +00002148 def add_options(self):
2149 testserver_base.TestServerRunner.add_options(self)
mshelley@chromium.orgf4d50372014-08-07 10:07:22 +00002150 self.option_parser.add_option('--disable-session-cache',
2151 action='store_true',
2152 dest='disable_session_cache',
2153 help='tells the server to disable the'
2154 'TLS session cache.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002155 self.option_parser.add_option('-f', '--ftp', action='store_const',
2156 const=SERVER_FTP, default=SERVER_HTTP,
2157 dest='server_type',
2158 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002159 self.option_parser.add_option('--tcp-echo', action='store_const',
2160 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2161 dest='server_type',
2162 help='start up a tcp echo server.')
2163 self.option_parser.add_option('--udp-echo', action='store_const',
2164 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2165 dest='server_type',
2166 help='start up a udp echo server.')
2167 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2168 const=SERVER_BASIC_AUTH_PROXY,
2169 default=SERVER_HTTP, dest='server_type',
2170 help='start up a proxy server which requires '
2171 'basic authentication.')
2172 self.option_parser.add_option('--websocket', action='store_const',
2173 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2174 dest='server_type',
2175 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002176 self.option_parser.add_option('--https', action='store_true',
2177 dest='https', help='Specify that https '
2178 'should be used.')
2179 self.option_parser.add_option('--cert-and-key-file',
2180 dest='cert_and_key_file', help='specify the '
2181 'path to the file containing the certificate '
2182 'and private key for the server in PEM '
2183 'format')
2184 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2185 help='The type of OCSP response generated '
2186 'for the automatically generated '
2187 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002188 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2189 default=0, type=int,
2190 help='If non-zero then the generated '
2191 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002192 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2193 default='0', type='int',
2194 help='If nonzero, certain TLS connections '
2195 'will be aborted in order to test version '
2196 'fallback. 1 means all TLS versions will be '
2197 'aborted. 2 means TLS 1.1 or higher will be '
2198 'aborted. 3 means TLS 1.2 or higher will be '
2199 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002200 self.option_parser.add_option('--tls-intolerance-type',
2201 dest='tls_intolerance_type',
2202 default="alert",
2203 help='Controls how the server reacts to a '
2204 'TLS version it is intolerant to. Valid '
2205 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002206 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2207 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002208 default='',
2209 help='Base64 encoded SCT list. If set, '
2210 'server will respond with a '
2211 'signed_certificate_timestamp TLS extension '
2212 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002213 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2214 default=False, const=True,
2215 action='store_const',
2216 help='If given, TLS_FALLBACK_SCSV support '
2217 'will be enabled. This causes the server to '
2218 'reject fallback connections from compatible '
2219 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002220 self.option_parser.add_option('--staple-ocsp-response',
2221 dest='staple_ocsp_response',
2222 default=False, action='store_true',
2223 help='If set, server will staple the OCSP '
2224 'response whenever OCSP is on and the client '
2225 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002226 self.option_parser.add_option('--https-record-resume',
2227 dest='record_resume', const=True,
2228 default=False, action='store_const',
2229 help='Record resumption cache events rather '
2230 'than resuming as normal. Allows the use of '
2231 'the /ssl-session-cache request')
2232 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2233 help='Require SSL client auth on every '
2234 'connection.')
2235 self.option_parser.add_option('--ssl-client-ca', action='append',
2236 default=[], help='Specify that the client '
2237 'certificate request should include the CA '
2238 'named in the subject of the DER-encoded '
2239 'certificate contained in the specified '
2240 'file. This option may appear multiple '
2241 'times, indicating multiple CA names should '
2242 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002243 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2244 default=[], help='Specify that the client '
2245 'certificate request should include the '
2246 'specified certificate_type value. This '
2247 'option may appear multiple times, '
2248 'indicating multiple values should be send '
2249 'in the request. Valid values are '
2250 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2251 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002252 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2253 help='Specify the bulk encryption '
2254 'algorithm(s) that will be accepted by the '
2255 'SSL server. Valid values are "aes256", '
2256 '"aes128", "3des", "rc4". If omitted, all '
2257 'algorithms will be used. This option may '
2258 'appear multiple times, indicating '
2259 'multiple algorithms should be enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002260 self.option_parser.add_option('--ssl-key-exchange', action='append',
2261 help='Specify the key exchange algorithm(s)'
2262 'that will be accepted by the SSL server. '
2263 'Valid values are "rsa", "dhe_rsa". If '
2264 'omitted, all algorithms will be used. This '
2265 'option may appear multiple times, '
2266 'indicating multiple algorithms should be '
2267 'enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002268 # TODO(davidben): Add ALPN support to tlslite.
2269 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2270 default=False, const=True,
2271 action='store_const',
2272 help='Enable server support for the NPN '
2273 'extension. The server will advertise '
2274 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002275 self.option_parser.add_option('--file-root-url', default='/files/',
2276 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002277 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2278 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2279 dest='ws_basic_auth',
2280 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002281 self.option_parser.add_option('--ocsp-server-unavailable',
2282 dest='ocsp_server_unavailable',
2283 default=False, action='store_true',
2284 help='If set, the OCSP server will return '
2285 'a tryLater status rather than the actual '
2286 'OCSP response.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002287
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002288
initial.commit94958cf2008-07-26 22:42:52 +00002289if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002290 sys.exit(ServerRunner().main())