blob: ad1a57d0e4f606294bd1e8801ca0e612107d5069 [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,
davidben21cda342015-03-17 18:04:28 -0700160 fallback_scsv_enabled, ocsp_response,
161 alert_after_handshake):
davidben@chromium.org7d53b542014-04-10 17:56:44 +0000162 self.cert_chain = tlslite.api.X509CertChain()
163 self.cert_chain.parsePemList(pem_cert_and_key)
phajdan.jr@chromium.org9e6098d2013-06-24 19:00:38 +0000164 # Force using only python implementation - otherwise behavior is different
165 # depending on whether m2crypto Python module is present (error is thrown
166 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
167 # the hood.
168 self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
169 private=True,
170 implementations=['python'])
davidben@chromium.org31282a12010-08-07 01:10:02 +0000171 self.ssl_client_auth = ssl_client_auth
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000172 self.ssl_client_cas = []
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000173 self.ssl_client_cert_types = []
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000174 if enable_npn:
175 self.next_protos = ['http/1.1']
176 else:
177 self.next_protos = None
ekasper@google.com24aa8222013-11-28 13:43:26 +0000178 self.signed_cert_timestamps = signed_cert_timestamps
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000179 self.fallback_scsv_enabled = fallback_scsv_enabled
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000180 self.ocsp_response = ocsp_response
agl@chromium.org143daa42012-04-26 18:45:34 +0000181
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000182 if ssl_client_auth:
183 for ca_file in ssl_client_cas:
184 s = open(ca_file).read()
185 x509 = tlslite.api.X509()
186 x509.parse(s)
187 self.ssl_client_cas.append(x509.subject)
188
189 for cert_type in ssl_client_cert_types:
190 self.ssl_client_cert_types.append({
191 "rsa_sign": tlslite.api.ClientCertificateType.rsa_sign,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000192 "ecdsa_sign": tlslite.api.ClientCertificateType.ecdsa_sign,
193 }[cert_type])
194
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000195 self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
davidbenc16cde32015-01-21 18:21:30 -0800196 # Enable SSLv3 for testing purposes.
197 self.ssl_handshake_settings.minVersion = (3, 0)
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000198 if ssl_bulk_ciphers is not None:
199 self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +0000200 if ssl_key_exchanges is not None:
201 self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +0000202 if tls_intolerant != 0:
203 self.ssl_handshake_settings.tlsIntolerant = (3, tls_intolerant)
204 self.ssl_handshake_settings.tlsIntoleranceType = tls_intolerance_type
davidben21cda342015-03-17 18:04:28 -0700205 if alert_after_handshake:
206 self.ssl_handshake_settings.alertAfterHandshake = True
initial.commit94958cf2008-07-26 22:42:52 +0000207
rsleevi8146efa2015-03-16 12:31:24 -0700208 if record_resume_info:
agl@chromium.orgf9e66792011-12-12 22:22:19 +0000209 # If record_resume_info is true then we'll replace the session cache with
210 # an object that records the lookups and inserts that it sees.
211 self.session_cache = RecordingSSLSessionCache()
212 else:
213 self.session_cache = tlslite.api.SessionCache()
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000214 testserver_base.StoppableHTTPServer.__init__(self,
215 server_address,
216 request_hander_class)
initial.commit94958cf2008-07-26 22:42:52 +0000217
218 def handshake(self, tlsConnection):
219 """Creates the SSL connection."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000220
initial.commit94958cf2008-07-26 22:42:52 +0000221 try:
agl@chromium.org04700be2013-03-02 18:40:41 +0000222 self.tlsConnection = tlsConnection
initial.commit94958cf2008-07-26 22:42:52 +0000223 tlsConnection.handshakeServer(certChain=self.cert_chain,
224 privateKey=self.private_key,
davidben@chromium.org31282a12010-08-07 01:10:02 +0000225 sessionCache=self.session_cache,
rsleevi@chromium.orgb2ecdab2010-08-21 04:02:44 +0000226 reqCert=self.ssl_client_auth,
rsleevi@chromium.org2124c812010-10-28 11:57:36 +0000227 settings=self.ssl_handshake_settings,
agl@chromium.org143daa42012-04-26 18:45:34 +0000228 reqCAs=self.ssl_client_cas,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +0000229 reqCertTypes=self.ssl_client_cert_types,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +0000230 nextProtos=self.next_protos,
ekasper@google.com24aa8222013-11-28 13:43:26 +0000231 signedCertTimestamps=
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +0000232 self.signed_cert_timestamps,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +0000233 fallbackSCSV=self.fallback_scsv_enabled,
234 ocspResponse = self.ocsp_response)
initial.commit94958cf2008-07-26 22:42:52 +0000235 tlsConnection.ignoreAbruptClose = True
236 return True
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +0000237 except tlslite.api.TLSAbruptCloseError:
238 # Ignore abrupt close.
239 return True
initial.commit94958cf2008-07-26 22:42:52 +0000240 except tlslite.api.TLSError, error:
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000241 print "Handshake failure:", str(error)
initial.commit94958cf2008-07-26 22:42:52 +0000242 return False
243
akalin@chromium.org154bb132010-11-12 02:20:27 +0000244
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000245class FTPServer(testserver_base.ClientRestrictingServerMixIn,
246 pyftpdlib.ftpserver.FTPServer):
erikwright@chromium.org847ef282012-02-22 16:41:10 +0000247 """This is a specialization of FTPServer that adds client verification."""
248
249 pass
250
251
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000252class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
253 SocketServer.TCPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000254 """A TCP echo server that echoes back what it has received."""
255
256 def server_bind(self):
257 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000258
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000259 SocketServer.TCPServer.server_bind(self)
260 host, port = self.socket.getsockname()[:2]
261 self.server_name = socket.getfqdn(host)
262 self.server_port = port
263
264 def serve_forever(self):
265 self.stop = False
266 self.nonce_time = None
267 while not self.stop:
268 self.handle_request()
269 self.socket.close()
270
271
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000272class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
273 SocketServer.UDPServer):
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000274 """A UDP echo server that echoes back what it has received."""
275
276 def server_bind(self):
277 """Override server_bind to store the server name."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000278
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +0000279 SocketServer.UDPServer.server_bind(self)
280 host, port = self.socket.getsockname()[:2]
281 self.server_name = socket.getfqdn(host)
282 self.server_port = port
283
284 def serve_forever(self):
285 self.stop = False
286 self.nonce_time = None
287 while not self.stop:
288 self.handle_request()
289 self.socket.close()
290
291
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000292class TestPageHandler(testserver_base.BasePageHandler):
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +0000293 # Class variables to allow for persistence state between page handler
294 # invocations
295 rst_limits = {}
296 fail_precondition = {}
initial.commit94958cf2008-07-26 22:42:52 +0000297
298 def __init__(self, request, client_address, socket_server):
akalin@chromium.org154bb132010-11-12 02:20:27 +0000299 connect_handlers = [
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000300 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000301 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000302 self.DefaultConnectResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000303 get_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000304 self.NoCacheMaxAgeTimeHandler,
305 self.NoCacheTimeHandler,
306 self.CacheTimeHandler,
307 self.CacheExpiresHandler,
308 self.CacheProxyRevalidateHandler,
309 self.CachePrivateHandler,
310 self.CachePublicHandler,
311 self.CacheSMaxAgeHandler,
312 self.CacheMustRevalidateHandler,
313 self.CacheMustRevalidateMaxAgeHandler,
314 self.CacheNoStoreHandler,
315 self.CacheNoStoreMaxAgeHandler,
316 self.CacheNoTransformHandler,
317 self.DownloadHandler,
318 self.DownloadFinishHandler,
319 self.EchoHeader,
ananta@chromium.org56812d02011-04-07 17:52:05 +0000320 self.EchoHeaderCache,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000321 self.EchoAllHandler,
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000322 self.ZipFileHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000323 self.FileHandler,
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +0000324 self.SetCookieHandler,
shalev@chromium.org9ede92f2012-06-14 22:40:34 +0000325 self.SetManyCookiesHandler,
mattm@chromium.org983fc462012-06-30 00:52:08 +0000326 self.ExpectAndSetCookieHandler,
battre@chromium.orgd4479e12011-11-07 17:09:19 +0000327 self.SetHeaderHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000328 self.AuthBasicHandler,
329 self.AuthDigestHandler,
330 self.SlowServerHandler,
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +0000331 self.ChunkedServerHandler,
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,
pneubeckfd4f0442015-08-07 04:55:10 -0700339 self.GetClientCert,
davidben599e7e72014-09-03 16:19:09 -0700340 self.ClientCipherListHandler,
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000341 self.CloseSocketHandler,
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +0000342 self.RangeResetHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000343 self.DefaultResponseHandler]
akalin@chromium.org154bb132010-11-12 02:20:27 +0000344 post_handlers = [
initial.commit94958cf2008-07-26 22:42:52 +0000345 self.EchoTitleHandler,
mnissler@chromium.org7c939802010-11-11 08:47:14 +0000346 self.EchoHandler,
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000347 self.PostOnlyFileHandler,
348 self.EchoMultipartPostHandler] + get_handlers
akalin@chromium.org154bb132010-11-12 02:20:27 +0000349 put_handlers = [
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000350 self.EchoTitleHandler,
akalin@chromium.org154bb132010-11-12 02:20:27 +0000351 self.EchoHandler] + get_handlers
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000352 head_handlers = [
353 self.FileHandler,
354 self.DefaultResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +0000355
maruel@google.come250a9b2009-03-10 17:39:46 +0000356 self._mime_types = {
rafaelw@chromium.orga4e76f82010-09-09 17:33:18 +0000357 'crx' : 'application/x-chrome-extension',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000358 'exe' : 'application/octet-stream',
maruel@google.come250a9b2009-03-10 17:39:46 +0000359 'gif': 'image/gif',
360 'jpeg' : 'image/jpeg',
finnur@chromium.org88e84c32009-10-02 17:59:55 +0000361 'jpg' : 'image/jpeg',
mvanouwerkerk348c1842014-10-23 09:07:34 -0700362 'js' : 'application/javascript',
satorux@chromium.orgfdc70122012-03-07 18:08:41 +0000363 'json': 'application/json',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000364 'pdf' : 'application/pdf',
dsjang@chromium.org3f4d97b2013-08-23 23:55:37 +0000365 'txt' : 'text/plain',
wolenetz@chromium.org6c74fb82013-01-09 00:38:34 +0000366 'wav' : 'audio/wav',
lzheng@chromium.org02f09022010-12-16 20:24:35 +0000367 'xml' : 'text/xml'
maruel@google.come250a9b2009-03-10 17:39:46 +0000368 }
initial.commit94958cf2008-07-26 22:42:52 +0000369 self._default_mime_type = 'text/html'
370
rsimha@chromium.org99a6f172013-01-20 01:10:24 +0000371 testserver_base.BasePageHandler.__init__(self, request, client_address,
372 socket_server, connect_handlers,
373 get_handlers, head_handlers,
374 post_handlers, put_handlers)
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000375
initial.commit94958cf2008-07-26 22:42:52 +0000376 def GetMIMETypeFromName(self, file_name):
377 """Returns the mime type for the specified file_name. So far it only looks
378 at the file extension."""
379
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000380 (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
initial.commit94958cf2008-07-26 22:42:52 +0000381 if len(extension) == 0:
382 # no extension.
383 return self._default_mime_type
384
ericroman@google.comc17ca532009-05-07 03:51:05 +0000385 # extension starts with a dot, so we need to remove it
386 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000387
initial.commit94958cf2008-07-26 22:42:52 +0000388 def NoCacheMaxAgeTimeHandler(self):
389 """This request handler yields a page with the title set to the current
390 system time, and no caching requested."""
391
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000392 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000393 return False
394
395 self.send_response(200)
396 self.send_header('Cache-Control', 'max-age=0')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000397 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000398 self.end_headers()
399
maruel@google.come250a9b2009-03-10 17:39:46 +0000400 self.wfile.write('<html><head><title>%s</title></head></html>' %
401 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000402
403 return True
404
405 def NoCacheTimeHandler(self):
406 """This request handler yields a page with the title set to the current
407 system time, and no caching requested."""
408
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000409 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000410 return False
411
412 self.send_response(200)
413 self.send_header('Cache-Control', 'no-cache')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000414 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000415 self.end_headers()
416
maruel@google.come250a9b2009-03-10 17:39:46 +0000417 self.wfile.write('<html><head><title>%s</title></head></html>' %
418 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000419
420 return True
421
422 def CacheTimeHandler(self):
423 """This request handler yields a page with the title set to the current
424 system time, and allows caching for one minute."""
425
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000426 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000427 return False
428
429 self.send_response(200)
430 self.send_header('Cache-Control', 'max-age=60')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000431 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000432 self.end_headers()
433
maruel@google.come250a9b2009-03-10 17:39:46 +0000434 self.wfile.write('<html><head><title>%s</title></head></html>' %
435 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000436
437 return True
438
439 def CacheExpiresHandler(self):
440 """This request handler yields a page with the title set to the current
441 system time, and set the page to expire on 1 Jan 2099."""
442
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000443 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000444 return False
445
446 self.send_response(200)
447 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000448 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000449 self.end_headers()
450
maruel@google.come250a9b2009-03-10 17:39:46 +0000451 self.wfile.write('<html><head><title>%s</title></head></html>' %
452 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000453
454 return True
455
456 def CacheProxyRevalidateHandler(self):
457 """This request handler yields a page with the title set to the current
458 system time, and allows caching for 60 seconds"""
459
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000460 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000461 return False
462
463 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000464 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000465 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
466 self.end_headers()
467
maruel@google.come250a9b2009-03-10 17:39:46 +0000468 self.wfile.write('<html><head><title>%s</title></head></html>' %
469 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000470
471 return True
472
473 def CachePrivateHandler(self):
474 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700475 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000476
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000477 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000478 return False
479
480 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000481 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000482 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000483 self.end_headers()
484
maruel@google.come250a9b2009-03-10 17:39:46 +0000485 self.wfile.write('<html><head><title>%s</title></head></html>' %
486 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000487
488 return True
489
490 def CachePublicHandler(self):
491 """This request handler yields a page with the title set to the current
twifkak9135cb92015-07-30 01:41:25 -0700492 system time, and allows caching for 3 seconds."""
initial.commit94958cf2008-07-26 22:42:52 +0000493
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000494 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000495 return False
496
497 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000498 self.send_header('Content-Type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000499 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000500 self.end_headers()
501
maruel@google.come250a9b2009-03-10 17:39:46 +0000502 self.wfile.write('<html><head><title>%s</title></head></html>' %
503 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000504
505 return True
506
507 def CacheSMaxAgeHandler(self):
508 """This request handler yields a page with the title set to the current
509 system time, and does not allow for caching."""
510
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000511 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000512 return False
513
514 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000515 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000516 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
517 self.end_headers()
518
maruel@google.come250a9b2009-03-10 17:39:46 +0000519 self.wfile.write('<html><head><title>%s</title></head></html>' %
520 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000521
522 return True
523
524 def CacheMustRevalidateHandler(self):
525 """This request handler yields a page with the title set to the current
526 system time, and does not allow caching."""
527
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000528 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000529 return False
530
531 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000532 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000533 self.send_header('Cache-Control', 'must-revalidate')
534 self.end_headers()
535
maruel@google.come250a9b2009-03-10 17:39:46 +0000536 self.wfile.write('<html><head><title>%s</title></head></html>' %
537 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000538
539 return True
540
541 def CacheMustRevalidateMaxAgeHandler(self):
542 """This request handler yields a page with the title set to the current
543 system time, and does not allow caching event though max-age of 60
544 seconds is specified."""
545
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000546 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000547 return False
548
549 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000550 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000551 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
552 self.end_headers()
553
maruel@google.come250a9b2009-03-10 17:39:46 +0000554 self.wfile.write('<html><head><title>%s</title></head></html>' %
555 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000556
557 return True
558
initial.commit94958cf2008-07-26 22:42:52 +0000559 def CacheNoStoreHandler(self):
560 """This request handler yields a page with the title set to the current
561 system time, and does not allow the page to be stored."""
562
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000563 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000564 return False
565
566 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000567 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000568 self.send_header('Cache-Control', 'no-store')
569 self.end_headers()
570
maruel@google.come250a9b2009-03-10 17:39:46 +0000571 self.wfile.write('<html><head><title>%s</title></head></html>' %
572 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000573
574 return True
575
576 def CacheNoStoreMaxAgeHandler(self):
577 """This request handler yields a page with the title set to the current
578 system time, and does not allow the page to be stored even though max-age
579 of 60 seconds is specified."""
580
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000581 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000582 return False
583
584 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000585 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000586 self.send_header('Cache-Control', 'max-age=60, no-store')
587 self.end_headers()
588
maruel@google.come250a9b2009-03-10 17:39:46 +0000589 self.wfile.write('<html><head><title>%s</title></head></html>' %
590 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000591
592 return True
593
594
595 def CacheNoTransformHandler(self):
596 """This request handler yields a page with the title set to the current
597 system time, and does not allow the content to transformed during
598 user-agent caching"""
599
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000600 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000601 return False
602
603 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000604 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000605 self.send_header('Cache-Control', 'no-transform')
606 self.end_headers()
607
maruel@google.come250a9b2009-03-10 17:39:46 +0000608 self.wfile.write('<html><head><title>%s</title></head></html>' %
609 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000610
611 return True
612
613 def EchoHeader(self):
614 """This handler echoes back the value of a specific request header."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000615
ananta@chromium.org219b2062009-10-23 16:09:41 +0000616 return self.EchoHeaderHelper("/echoheader")
initial.commit94958cf2008-07-26 22:42:52 +0000617
ananta@chromium.org56812d02011-04-07 17:52:05 +0000618 def EchoHeaderCache(self):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000619 """This function echoes back the value of a specific request header while
620 allowing caching for 16 hours."""
621
ananta@chromium.org56812d02011-04-07 17:52:05 +0000622 return self.EchoHeaderHelper("/echoheadercache")
ananta@chromium.org219b2062009-10-23 16:09:41 +0000623
624 def EchoHeaderHelper(self, echo_header):
625 """This function echoes back the value of the request header passed in."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000626
ananta@chromium.org219b2062009-10-23 16:09:41 +0000627 if not self._ShouldHandleRequest(echo_header):
initial.commit94958cf2008-07-26 22:42:52 +0000628 return False
629
630 query_char = self.path.find('?')
631 if query_char != -1:
632 header_name = self.path[query_char+1:]
633
634 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000635 self.send_header('Content-Type', 'text/plain')
ananta@chromium.org56812d02011-04-07 17:52:05 +0000636 if echo_header == '/echoheadercache':
637 self.send_header('Cache-control', 'max-age=60000')
638 else:
639 self.send_header('Cache-control', 'no-cache')
initial.commit94958cf2008-07-26 22:42:52 +0000640 # insert a vary header to properly indicate that the cachability of this
641 # request is subject to value of the request header being echoed.
642 if len(header_name) > 0:
643 self.send_header('Vary', header_name)
644 self.end_headers()
645
646 if len(header_name) > 0:
647 self.wfile.write(self.headers.getheader(header_name))
648
649 return True
650
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000651 def ReadRequestBody(self):
652 """This function reads the body of the current HTTP request, handling
653 both plain and chunked transfer encoded requests."""
654
655 if self.headers.getheader('transfer-encoding') != 'chunked':
656 length = int(self.headers.getheader('content-length'))
657 return self.rfile.read(length)
658
659 # Read the request body as chunks.
660 body = ""
661 while True:
662 line = self.rfile.readline()
663 length = int(line, 16)
664 if length == 0:
665 self.rfile.readline()
666 break
667 body += self.rfile.read(length)
668 self.rfile.read(2)
669 return body
670
initial.commit94958cf2008-07-26 22:42:52 +0000671 def EchoHandler(self):
672 """This handler just echoes back the payload of the request, for testing
673 form submission."""
674
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000675 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000676 return False
677
hirono2838c572015-01-21 12:18:11 -0800678 _, _, _, _, query, _ = urlparse.urlparse(self.path)
679 query_params = cgi.parse_qs(query, True)
680 if 'status' in query_params:
681 self.send_response(int(query_params['status'][0]))
682 else:
683 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000684 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000685 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000686 self.wfile.write(self.ReadRequestBody())
initial.commit94958cf2008-07-26 22:42:52 +0000687 return True
688
689 def EchoTitleHandler(self):
690 """This handler is like Echo, but sets the page title to the request."""
691
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000692 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000693 return False
694
695 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000696 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000697 self.end_headers()
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000698 request = self.ReadRequestBody()
initial.commit94958cf2008-07-26 22:42:52 +0000699 self.wfile.write('<html><head><title>')
700 self.wfile.write(request)
701 self.wfile.write('</title></head></html>')
702 return True
703
704 def EchoAllHandler(self):
705 """This handler yields a (more) human-readable page listing information
706 about the request header & contents."""
707
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000708 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000709 return False
710
711 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000712 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000713 self.end_headers()
714 self.wfile.write('<html><head><style>'
715 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
716 '</style></head><body>'
717 '<div style="float: right">'
cbentzel@chromium.org0787bc72010-11-11 20:31:31 +0000718 '<a href="/echo">back to referring page</a></div>'
initial.commit94958cf2008-07-26 22:42:52 +0000719 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000720
ananta@chromium.org56d146f2010-01-11 19:03:01 +0000721 if self.command == 'POST' or self.command == 'PUT':
satish@chromium.orgce0b1d02011-01-25 07:17:11 +0000722 qs = self.ReadRequestBody()
ericroman@google.coma47622b2008-11-15 04:36:51 +0000723 params = cgi.parse_qs(qs, keep_blank_values=1)
724
725 for param in params:
726 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000727
728 self.wfile.write('</pre>')
729
730 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
731
732 self.wfile.write('</body></html>')
733 return True
734
kkimlabs@chromium.org622395e2013-08-22 22:50:55 +0000735 def EchoMultipartPostHandler(self):
736 """This handler echoes received multipart post data as json format."""
737
738 if not (self._ShouldHandleRequest("/echomultipartpost") or
739 self._ShouldHandleRequest("/searchbyimage")):
740 return False
741
742 content_type, parameters = cgi.parse_header(
743 self.headers.getheader('content-type'))
744 if content_type == 'multipart/form-data':
745 post_multipart = cgi.parse_multipart(self.rfile, parameters)
746 elif content_type == 'application/x-www-form-urlencoded':
747 raise Exception('POST by application/x-www-form-urlencoded is '
748 'not implemented.')
749 else:
750 post_multipart = {}
751
752 # Since the data can be binary, we encode them by base64.
753 post_multipart_base64_encoded = {}
754 for field, values in post_multipart.items():
755 post_multipart_base64_encoded[field] = [base64.b64encode(value)
756 for value in values]
757
758 result = {'POST_multipart' : post_multipart_base64_encoded}
759
760 self.send_response(200)
761 self.send_header("Content-type", "text/plain")
762 self.end_headers()
763 self.wfile.write(json.dumps(result, indent=2, sort_keys=False))
764 return True
765
initial.commit94958cf2008-07-26 22:42:52 +0000766 def DownloadHandler(self):
767 """This handler sends a downloadable file with or without reporting
768 the size (6K)."""
769
770 if self.path.startswith("/download-unknown-size"):
771 send_length = False
772 elif self.path.startswith("/download-known-size"):
773 send_length = True
774 else:
775 return False
776
777 #
778 # The test which uses this functionality is attempting to send
779 # small chunks of data to the client. Use a fairly large buffer
780 # so that we'll fill chrome's IO buffer enough to force it to
781 # actually write the data.
782 # See also the comments in the client-side of this test in
783 # download_uitest.cc
784 #
785 size_chunk1 = 35*1024
786 size_chunk2 = 10*1024
787
788 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000789 self.send_header('Content-Type', 'application/octet-stream')
initial.commit94958cf2008-07-26 22:42:52 +0000790 self.send_header('Cache-Control', 'max-age=0')
791 if send_length:
792 self.send_header('Content-Length', size_chunk1 + size_chunk2)
793 self.end_headers()
794
795 # First chunk of data:
796 self.wfile.write("*" * size_chunk1)
797 self.wfile.flush()
798
799 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000800 self.server.wait_for_download = True
801 while self.server.wait_for_download:
initial.commit94958cf2008-07-26 22:42:52 +0000802 self.server.handle_request()
803
804 # Second chunk of data:
805 self.wfile.write("*" * size_chunk2)
806 return True
807
808 def DownloadFinishHandler(self):
809 """This handler just tells the server to finish the current download."""
810
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000811 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000812 return False
813
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +0000814 self.server.wait_for_download = False
initial.commit94958cf2008-07-26 22:42:52 +0000815 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000816 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +0000817 self.send_header('Cache-Control', 'max-age=0')
818 self.end_headers()
819 return True
820
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000821 def _ReplaceFileData(self, data, query_parameters):
822 """Replaces matching substrings in a file.
823
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000824 If the 'replace_text' URL query parameter is present, it is expected to be
825 of the form old_text:new_text, which indicates that any old_text strings in
826 the file are replaced with new_text. Multiple 'replace_text' parameters may
827 be specified.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000828
829 If the parameters are not present, |data| is returned.
830 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000831
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000832 query_dict = cgi.parse_qs(query_parameters)
cbentzel@chromium.org099a3db2010-11-11 18:16:58 +0000833 replace_text_values = query_dict.get('replace_text', [])
834 for replace_text_value in replace_text_values:
835 replace_text_args = replace_text_value.split(':')
836 if len(replace_text_args) != 2:
837 raise ValueError(
838 'replace_text must be of form old_text:new_text. Actual value: %s' %
839 replace_text_value)
840 old_text_b64, new_text_b64 = replace_text_args
841 old_text = base64.urlsafe_b64decode(old_text_b64)
842 new_text = base64.urlsafe_b64decode(new_text_b64)
843 data = data.replace(old_text, new_text)
844 return data
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000845
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000846 def ZipFileHandler(self):
847 """This handler sends the contents of the requested file in compressed form.
848 Can pass in a parameter that specifies that the content length be
849 C - the compressed size (OK),
850 U - the uncompressed size (Non-standard, but handled),
851 S - less than compressed (OK because we keep going),
852 M - larger than compressed but less than uncompressed (an error),
853 L - larger than uncompressed (an error)
854 Example: compressedfiles/Picture_1.doc?C
855 """
856
857 prefix = "/compressedfiles/"
858 if not self.path.startswith(prefix):
859 return False
860
861 # Consume a request body if present.
862 if self.command == 'POST' or self.command == 'PUT' :
863 self.ReadRequestBody()
864
865 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
866
867 if not query in ('C', 'U', 'S', 'M', 'L'):
868 return False
869
870 sub_path = url_path[len(prefix):]
871 entries = sub_path.split('/')
872 file_path = os.path.join(self.server.data_dir, *entries)
873 if os.path.isdir(file_path):
874 file_path = os.path.join(file_path, 'index.html')
875
876 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000877 print "File not found " + sub_path + " full path:" + file_path
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000878 self.send_error(404)
879 return True
880
881 f = open(file_path, "rb")
882 data = f.read()
883 uncompressed_len = len(data)
884 f.close()
885
886 # Compress the data.
887 data = zlib.compress(data)
888 compressed_len = len(data)
889
890 content_length = compressed_len
891 if query == 'U':
892 content_length = uncompressed_len
893 elif query == 'S':
894 content_length = compressed_len / 2
895 elif query == 'M':
896 content_length = (compressed_len + uncompressed_len) / 2
897 elif query == 'L':
898 content_length = compressed_len + uncompressed_len
899
900 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +0000901 self.send_header('Content-Type', 'application/msword')
ahendrickson@chromium.orgab17b6a2011-05-24 20:14:39 +0000902 self.send_header('Content-encoding', 'deflate')
903 self.send_header('Connection', 'close')
904 self.send_header('Content-Length', content_length)
905 self.send_header('ETag', '\'' + file_path + '\'')
906 self.end_headers()
907
908 self.wfile.write(data)
909
910 return True
911
initial.commit94958cf2008-07-26 22:42:52 +0000912 def FileHandler(self):
913 """This handler sends the contents of the requested file. Wow, it's like
914 a real webserver!"""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000915
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000916 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000917 if not self.path.startswith(prefix):
918 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000919 return self._FileHandlerHelper(prefix)
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000920
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000921 def PostOnlyFileHandler(self):
922 """This handler sends the contents of the requested file on a POST."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000923
cbentzel@chromium.org4f3a9412012-01-31 23:47:20 +0000924 prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000925 if not self.path.startswith(prefix):
926 return False
cbentzel@chromium.org18fc5562012-01-13 13:27:44 +0000927 return self._FileHandlerHelper(prefix)
928
929 def _FileHandlerHelper(self, prefix):
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000930 request_body = ''
931 if self.command == 'POST' or self.command == 'PUT':
932 # Consume a request body if present.
933 request_body = self.ReadRequestBody()
934
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000935 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000936 query_dict = cgi.parse_qs(query)
937
938 expected_body = query_dict.get('expected_body', [])
939 if expected_body and request_body not in expected_body:
940 self.send_response(404)
941 self.end_headers()
942 self.wfile.write('')
943 return True
944
945 expected_headers = query_dict.get('expected_headers', [])
946 for expected_header in expected_headers:
947 header_name, expected_value = expected_header.split(':')
948 if self.headers.getheader(header_name) != expected_value:
949 self.send_response(404)
950 self.end_headers()
951 self.wfile.write('')
952 return True
953
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000954 sub_path = url_path[len(prefix):]
955 entries = sub_path.split('/')
956 file_path = os.path.join(self.server.data_dir, *entries)
957 if os.path.isdir(file_path):
958 file_path = os.path.join(file_path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000959
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000960 if not os.path.isfile(file_path):
wjia@chromium.orgff532f32013-03-18 19:23:44 +0000961 print "File not found " + sub_path + " full path:" + file_path
initial.commit94958cf2008-07-26 22:42:52 +0000962 self.send_error(404)
963 return True
964
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000965 f = open(file_path, "rb")
initial.commit94958cf2008-07-26 22:42:52 +0000966 data = f.read()
967 f.close()
968
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000969 data = self._ReplaceFileData(data, query)
970
benjhayden@chromium.org77ea5442012-02-14 23:29:37 +0000971 old_protocol_version = self.protocol_version
972
initial.commit94958cf2008-07-26 22:42:52 +0000973 # If file.mock-http-headers exists, it contains the headers we
974 # should send. Read them in and parse them.
cbentzel@chromium.orge30b32d2010-11-06 17:33:56 +0000975 headers_path = file_path + '.mock-http-headers'
initial.commit94958cf2008-07-26 22:42:52 +0000976 if os.path.isfile(headers_path):
977 f = open(headers_path, "r")
978
979 # "HTTP/1.1 200 OK"
980 response = f.readline()
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +0000981 http_major, http_minor, status_code = re.findall(
982 'HTTP/(\d+).(\d+) (\d+)', response)[0]
983 self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
initial.commit94958cf2008-07-26 22:42:52 +0000984 self.send_response(int(status_code))
985
986 for line in f:
robertshield@chromium.org5e231612010-01-20 18:23:53 +0000987 header_values = re.findall('(\S+):\s*(.*)', line)
988 if len(header_values) > 0:
989 # "name: value"
990 name, value = header_values[0]
991 self.send_header(name, value)
initial.commit94958cf2008-07-26 22:42:52 +0000992 f.close()
993 else:
994 # Could be more generic once we support mime-type sniffing, but for
995 # now we need to set it explicitly.
jam@chromium.org41550782010-11-17 23:47:50 +0000996
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +0000997 range_header = self.headers.get('Range')
998 if range_header and range_header.startswith('bytes='):
999 # Note this doesn't handle all valid byte range_header values (i.e.
1000 # left open ended ones), just enough for what we needed so far.
1001 range_header = range_header[6:].split('-')
1002 start = int(range_header[0])
1003 if range_header[1]:
1004 end = int(range_header[1])
shishir@chromium.orge6444822011-12-09 02:45:44 +00001005 else:
fischman@chromium.orgd4f2e722012-03-16 20:57:26 +00001006 end = len(data) - 1
jam@chromium.org41550782010-11-17 23:47:50 +00001007
1008 self.send_response(206)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001009 content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
1010 str(len(data)))
jam@chromium.org41550782010-11-17 23:47:50 +00001011 self.send_header('Content-Range', content_range)
1012 data = data[start: end + 1]
1013 else:
1014 self.send_response(200)
1015
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001016 self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
jam@chromium.org41550782010-11-17 23:47:50 +00001017 self.send_header('Accept-Ranges', 'bytes')
initial.commit94958cf2008-07-26 22:42:52 +00001018 self.send_header('Content-Length', len(data))
jam@chromium.org41550782010-11-17 23:47:50 +00001019 self.send_header('ETag', '\'' + file_path + '\'')
initial.commit94958cf2008-07-26 22:42:52 +00001020 self.end_headers()
1021
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001022 if (self.command != 'HEAD'):
1023 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001024
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001025 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001026 return True
1027
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001028 def SetCookieHandler(self):
1029 """This handler just sets a cookie, for testing cookie handling."""
1030
1031 if not self._ShouldHandleRequest("/set-cookie"):
1032 return False
1033
1034 query_char = self.path.find('?')
1035 if query_char != -1:
1036 cookie_values = self.path[query_char + 1:].split('&')
1037 else:
1038 cookie_values = ("",)
1039 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001040 self.send_header('Content-Type', 'text/html')
levin@chromium.orgf7ee2e42009-08-26 02:33:46 +00001041 for cookie_value in cookie_values:
1042 self.send_header('Set-Cookie', '%s' % cookie_value)
1043 self.end_headers()
1044 for cookie_value in cookie_values:
1045 self.wfile.write('%s' % cookie_value)
1046 return True
1047
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001048 def SetManyCookiesHandler(self):
1049 """This handler just sets a given number of cookies, for testing handling
1050 of large numbers of cookies."""
1051
1052 if not self._ShouldHandleRequest("/set-many-cookies"):
1053 return False
1054
1055 query_char = self.path.find('?')
1056 if query_char != -1:
1057 num_cookies = int(self.path[query_char + 1:])
1058 else:
1059 num_cookies = 0
1060 self.send_response(200)
1061 self.send_header('', 'text/html')
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001062 for _i in range(0, num_cookies):
shalev@chromium.org9ede92f2012-06-14 22:40:34 +00001063 self.send_header('Set-Cookie', 'a=')
1064 self.end_headers()
1065 self.wfile.write('%d cookies were sent' % num_cookies)
1066 return True
1067
mattm@chromium.org983fc462012-06-30 00:52:08 +00001068 def ExpectAndSetCookieHandler(self):
1069 """Expects some cookies to be sent, and if they are, sets more cookies.
1070
1071 The expect parameter specifies a required cookie. May be specified multiple
1072 times.
1073 The set parameter specifies a cookie to set if all required cookies are
1074 preset. May be specified multiple times.
1075 The data parameter specifies the response body data to be returned."""
1076
1077 if not self._ShouldHandleRequest("/expect-and-set-cookie"):
1078 return False
1079
1080 _, _, _, _, query, _ = urlparse.urlparse(self.path)
1081 query_dict = cgi.parse_qs(query)
1082 cookies = set()
1083 if 'Cookie' in self.headers:
1084 cookie_header = self.headers.getheader('Cookie')
1085 cookies.update([s.strip() for s in cookie_header.split(';')])
1086 got_all_expected_cookies = True
1087 for expected_cookie in query_dict.get('expect', []):
1088 if expected_cookie not in cookies:
1089 got_all_expected_cookies = False
1090 self.send_response(200)
1091 self.send_header('Content-Type', 'text/html')
1092 if got_all_expected_cookies:
1093 for cookie_value in query_dict.get('set', []):
1094 self.send_header('Set-Cookie', '%s' % cookie_value)
1095 self.end_headers()
1096 for data_value in query_dict.get('data', []):
1097 self.wfile.write(data_value)
1098 return True
1099
battre@chromium.orgd4479e12011-11-07 17:09:19 +00001100 def SetHeaderHandler(self):
1101 """This handler sets a response header. Parameters are in the
1102 key%3A%20value&key2%3A%20value2 format."""
1103
1104 if not self._ShouldHandleRequest("/set-header"):
1105 return False
1106
1107 query_char = self.path.find('?')
1108 if query_char != -1:
1109 headers_values = self.path[query_char + 1:].split('&')
1110 else:
1111 headers_values = ("",)
1112 self.send_response(200)
1113 self.send_header('Content-Type', 'text/html')
1114 for header_value in headers_values:
1115 header_value = urllib.unquote(header_value)
1116 (key, value) = header_value.split(': ', 1)
1117 self.send_header(key, value)
1118 self.end_headers()
1119 for header_value in headers_values:
1120 self.wfile.write('%s' % header_value)
1121 return True
1122
initial.commit94958cf2008-07-26 22:42:52 +00001123 def AuthBasicHandler(self):
1124 """This handler tests 'Basic' authentication. It just sends a page with
1125 title 'user/pass' if you succeed."""
1126
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001127 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +00001128 return False
1129
1130 username = userpass = password = b64str = ""
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001131 expected_password = 'secret'
1132 realm = 'testrealm'
1133 set_cookie_if_challenged = False
initial.commit94958cf2008-07-26 22:42:52 +00001134
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001135 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1136 query_params = cgi.parse_qs(query, True)
1137 if 'set-cookie-if-challenged' in query_params:
1138 set_cookie_if_challenged = True
1139 if 'password' in query_params:
1140 expected_password = query_params['password'][0]
1141 if 'realm' in query_params:
1142 realm = query_params['realm'][0]
ericroman@google.com239b4d82009-03-27 04:00:22 +00001143
initial.commit94958cf2008-07-26 22:42:52 +00001144 auth = self.headers.getheader('authorization')
1145 try:
1146 if not auth:
1147 raise Exception('no auth')
1148 b64str = re.findall(r'Basic (\S+)', auth)[0]
1149 userpass = base64.b64decode(b64str)
1150 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001151 if password != expected_password:
initial.commit94958cf2008-07-26 22:42:52 +00001152 raise Exception('wrong password')
1153 except Exception, e:
1154 # Authentication failed.
1155 self.send_response(401)
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001156 self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001157 self.send_header('Content-Type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +00001158 if set_cookie_if_challenged:
1159 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +00001160 self.end_headers()
1161 self.wfile.write('<html><head>')
1162 self.wfile.write('<title>Denied: %s</title>' % e)
1163 self.wfile.write('</head><body>')
1164 self.wfile.write('auth=%s<p>' % auth)
1165 self.wfile.write('b64str=%s<p>' % b64str)
1166 self.wfile.write('username: %s<p>' % username)
1167 self.wfile.write('userpass: %s<p>' % userpass)
1168 self.wfile.write('password: %s<p>' % password)
1169 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1170 self.wfile.write('</body></html>')
1171 return True
1172
1173 # Authentication successful. (Return a cachable response to allow for
1174 # testing cached pages that require authentication.)
rvargas@google.com54453b72011-05-19 01:11:11 +00001175 old_protocol_version = self.protocol_version
1176 self.protocol_version = "HTTP/1.1"
1177
initial.commit94958cf2008-07-26 22:42:52 +00001178 if_none_match = self.headers.getheader('if-none-match')
1179 if if_none_match == "abc":
1180 self.send_response(304)
1181 self.end_headers()
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001182 elif url_path.endswith(".gif"):
1183 # Using chrome/test/data/google/logo.gif as the test image
1184 test_image_path = ['google', 'logo.gif']
1185 gif_path = os.path.join(self.server.data_dir, *test_image_path)
1186 if not os.path.isfile(gif_path):
1187 self.send_error(404)
rvargas@google.com54453b72011-05-19 01:11:11 +00001188 self.protocol_version = old_protocol_version
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001189 return True
1190
1191 f = open(gif_path, "rb")
1192 data = f.read()
1193 f.close()
1194
1195 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001196 self.send_header('Content-Type', 'image/gif')
cbentzel@chromium.org5a808d22011-01-05 15:51:24 +00001197 self.send_header('Cache-control', 'max-age=60000')
1198 self.send_header('Etag', 'abc')
1199 self.end_headers()
1200 self.wfile.write(data)
initial.commit94958cf2008-07-26 22:42:52 +00001201 else:
1202 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001203 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001204 self.send_header('Cache-control', 'max-age=60000')
1205 self.send_header('Etag', 'abc')
1206 self.end_headers()
1207 self.wfile.write('<html><head>')
1208 self.wfile.write('<title>%s/%s</title>' % (username, password))
1209 self.wfile.write('</head><body>')
1210 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +00001211 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +00001212 self.wfile.write('</body></html>')
1213
rvargas@google.com54453b72011-05-19 01:11:11 +00001214 self.protocol_version = old_protocol_version
initial.commit94958cf2008-07-26 22:42:52 +00001215 return True
1216
tonyg@chromium.org75054202010-03-31 22:06:10 +00001217 def GetNonce(self, force_reset=False):
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001218 """Returns a nonce that's stable per request path for the server's lifetime.
1219 This is a fake implementation. A real implementation would only use a given
1220 nonce a single time (hence the name n-once). However, for the purposes of
1221 unittesting, we don't care about the security of the nonce.
initial.commit94958cf2008-07-26 22:42:52 +00001222
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001223 Args:
1224 force_reset: Iff set, the nonce will be changed. Useful for testing the
1225 "stale" response.
1226 """
tonyg@chromium.org75054202010-03-31 22:06:10 +00001227
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001228 if force_reset or not self.server.nonce_time:
1229 self.server.nonce_time = time.time()
1230 return hashlib.md5('privatekey%s%d' %
1231 (self.path, self.server.nonce_time)).hexdigest()
tonyg@chromium.org75054202010-03-31 22:06:10 +00001232
1233 def AuthDigestHandler(self):
1234 """This handler tests 'Digest' authentication.
1235
1236 It just sends a page with title 'user/pass' if you succeed.
1237
1238 A stale response is sent iff "stale" is present in the request path.
1239 """
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001240
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001241 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +00001242 return False
1243
tonyg@chromium.org75054202010-03-31 22:06:10 +00001244 stale = 'stale' in self.path
1245 nonce = self.GetNonce(force_reset=stale)
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001246 opaque = hashlib.md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001247 password = 'secret'
1248 realm = 'testrealm'
1249
1250 auth = self.headers.getheader('authorization')
1251 pairs = {}
1252 try:
1253 if not auth:
1254 raise Exception('no auth')
1255 if not auth.startswith('Digest'):
1256 raise Exception('not digest')
1257 # Pull out all the name="value" pairs as a dictionary.
1258 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1259
1260 # Make sure it's all valid.
1261 if pairs['nonce'] != nonce:
1262 raise Exception('wrong nonce')
1263 if pairs['opaque'] != opaque:
1264 raise Exception('wrong opaque')
1265
1266 # Check the 'response' value and make sure it matches our magic hash.
1267 # See http://www.ietf.org/rfc/rfc2617.txt
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001268 hash_a1 = hashlib.md5(
maruel@google.come250a9b2009-03-10 17:39:46 +00001269 ':'.join([pairs['username'], realm, password])).hexdigest()
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001270 hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001271 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001272 response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +00001273 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1274 else:
mattm@chromium.org11f17fb2012-09-23 00:06:27 +00001275 response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +00001276
1277 if pairs['response'] != response:
1278 raise Exception('wrong password')
1279 except Exception, e:
1280 # Authentication failed.
1281 self.send_response(401)
1282 hdr = ('Digest '
1283 'realm="%s", '
1284 'domain="/", '
1285 'qop="auth", '
1286 'algorithm=MD5, '
1287 'nonce="%s", '
1288 'opaque="%s"') % (realm, nonce, opaque)
1289 if stale:
1290 hdr += ', stale="TRUE"'
1291 self.send_header('WWW-Authenticate', hdr)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001292 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001293 self.end_headers()
1294 self.wfile.write('<html><head>')
1295 self.wfile.write('<title>Denied: %s</title>' % e)
1296 self.wfile.write('</head><body>')
1297 self.wfile.write('auth=%s<p>' % auth)
1298 self.wfile.write('pairs=%s<p>' % pairs)
1299 self.wfile.write('You sent:<br>%s<p>' % self.headers)
1300 self.wfile.write('We are replying:<br>%s<p>' % hdr)
1301 self.wfile.write('</body></html>')
1302 return True
1303
1304 # Authentication successful.
1305 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001306 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001307 self.end_headers()
1308 self.wfile.write('<html><head>')
1309 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1310 self.wfile.write('</head><body>')
1311 self.wfile.write('auth=%s<p>' % auth)
1312 self.wfile.write('pairs=%s<p>' % pairs)
1313 self.wfile.write('</body></html>')
1314
1315 return True
1316
1317 def SlowServerHandler(self):
1318 """Wait for the user suggested time before responding. The syntax is
1319 /slow?0.5 to wait for half a second."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001320
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001321 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +00001322 return False
1323 query_char = self.path.find('?')
1324 wait_sec = 1.0
1325 if query_char >= 0:
1326 try:
davidben05f82202015-03-31 13:48:07 -07001327 wait_sec = float(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001328 except ValueError:
1329 pass
1330 time.sleep(wait_sec)
1331 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001332 self.send_header('Content-Type', 'text/plain')
initial.commit94958cf2008-07-26 22:42:52 +00001333 self.end_headers()
davidben05f82202015-03-31 13:48:07 -07001334 self.wfile.write("waited %.1f seconds" % wait_sec)
initial.commit94958cf2008-07-26 22:42:52 +00001335 return True
1336
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001337 def ChunkedServerHandler(self):
1338 """Send chunked response. Allows to specify chunks parameters:
1339 - waitBeforeHeaders - ms to wait before sending headers
1340 - waitBetweenChunks - ms to wait between chunks
1341 - chunkSize - size of each chunk in bytes
1342 - chunksNumber - number of chunks
1343 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1344 waits one second, then sends headers and five chunks five bytes each."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001345
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001346 if not self._ShouldHandleRequest("/chunked"):
1347 return False
1348 query_char = self.path.find('?')
1349 chunkedSettings = {'waitBeforeHeaders' : 0,
1350 'waitBetweenChunks' : 0,
1351 'chunkSize' : 5,
1352 'chunksNumber' : 5}
1353 if query_char >= 0:
1354 params = self.path[query_char + 1:].split('&')
1355 for param in params:
1356 keyValue = param.split('=')
1357 if len(keyValue) == 2:
1358 try:
1359 chunkedSettings[keyValue[0]] = int(keyValue[1])
1360 except ValueError:
1361 pass
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001362 time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001363 self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1364 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001365 self.send_header('Content-Type', 'text/plain')
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001366 self.send_header('Connection', 'close')
1367 self.send_header('Transfer-Encoding', 'chunked')
1368 self.end_headers()
1369 # Chunked encoding: sending all chunks, then final zero-length chunk and
1370 # then final CRLF.
1371 for i in range(0, chunkedSettings['chunksNumber']):
1372 if i > 0:
1373 time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1374 self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001375 self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001376 self.sendChunkHelp('')
1377 return True
1378
creis@google.com2f4f6a42011-03-25 19:44:19 +00001379 def NoContentHandler(self):
1380 """Returns a 204 No Content response."""
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001381
creis@google.com2f4f6a42011-03-25 19:44:19 +00001382 if not self._ShouldHandleRequest("/nocontent"):
1383 return False
1384 self.send_response(204)
1385 self.end_headers()
1386 return True
1387
initial.commit94958cf2008-07-26 22:42:52 +00001388 def ServerRedirectHandler(self):
1389 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001390 '/server-redirect?http://foo.bar/asdf' to redirect to
1391 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001392
1393 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001394 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001395 return False
1396
1397 query_char = self.path.find('?')
1398 if query_char < 0 or len(self.path) <= query_char + 1:
1399 self.sendRedirectHelp(test_name)
1400 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001401 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001402
1403 self.send_response(301) # moved permanently
1404 self.send_header('Location', dest)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001405 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001406 self.end_headers()
1407 self.wfile.write('<html><head>')
1408 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1409
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001410 return True
initial.commit94958cf2008-07-26 22:42:52 +00001411
naskoe7a0d0d2014-09-29 08:53:05 -07001412 def CrossSiteRedirectHandler(self):
1413 """Sends a server redirect to the given site. The syntax is
1414 '/cross-site/hostname/...' to redirect to //hostname/...
1415 It is used to navigate between different Sites, causing
1416 cross-site/cross-process navigations in the browser."""
1417
1418 test_name = "/cross-site"
1419 if not self._ShouldHandleRequest(test_name):
naskoe7a0d0d2014-09-29 08:53:05 -07001420 return False
1421
1422 params = urllib.unquote(self.path[(len(test_name) + 1):])
1423 slash = params.find('/')
1424 if slash < 0:
1425 self.sendRedirectHelp(test_name)
1426 return True
1427
1428 host = params[:slash]
1429 path = params[(slash+1):]
1430 dest = "//%s:%s/%s" % (host, str(self.server.server_port), path)
1431
1432 self.send_response(301) # moved permanently
1433 self.send_header('Location', dest)
1434 self.send_header('Content-Type', 'text/html')
1435 self.end_headers()
1436 self.wfile.write('<html><head>')
1437 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1438
1439 return True
1440
initial.commit94958cf2008-07-26 22:42:52 +00001441 def ClientRedirectHandler(self):
1442 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +00001443 '/client-redirect?http://foo.bar/asdf' to redirect to
1444 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +00001445
1446 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001447 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +00001448 return False
1449
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001450 query_char = self.path.find('?')
initial.commit94958cf2008-07-26 22:42:52 +00001451 if query_char < 0 or len(self.path) <= query_char + 1:
1452 self.sendRedirectHelp(test_name)
1453 return True
davidben@chromium.orgc3e1fc72013-09-18 01:17:38 +00001454 dest = urllib.unquote(self.path[query_char + 1:])
initial.commit94958cf2008-07-26 22:42:52 +00001455
1456 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001457 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001458 self.end_headers()
1459 self.wfile.write('<html><head>')
1460 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1461 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1462
1463 return True
1464
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001465 def GetSSLSessionCacheHandler(self):
1466 """Send a reply containing a log of the session cache operations."""
1467
1468 if not self._ShouldHandleRequest('/ssl-session-cache'):
1469 return False
1470
1471 self.send_response(200)
1472 self.send_header('Content-Type', 'text/plain')
1473 self.end_headers()
1474 try:
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001475 log = self.server.session_cache.log
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001476 except AttributeError:
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001477 self.wfile.write('Pass --https-record-resume in order to use' +
1478 ' this request')
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001479 return True
1480
1481 for (action, sessionID) in log:
1482 self.wfile.write('%s\t%s\n' % (action, bytes(sessionID).encode('hex')))
agl@chromium.orgf9e66792011-12-12 22:22:19 +00001483 return True
1484
rsleevi@chromium.org6bb9f042013-02-16 04:10:07 +00001485 def SSLManySmallRecords(self):
1486 """Sends a reply consisting of a variety of small writes. These will be
1487 translated into a series of small SSL records when used over an HTTPS
1488 server."""
1489
1490 if not self._ShouldHandleRequest('/ssl-many-small-records'):
1491 return False
1492
1493 self.send_response(200)
1494 self.send_header('Content-Type', 'text/plain')
1495 self.end_headers()
1496
1497 # Write ~26K of data, in 1350 byte chunks
1498 for i in xrange(20):
1499 self.wfile.write('*' * 1350)
1500 self.wfile.flush()
1501 return True
1502
agl@chromium.org04700be2013-03-02 18:40:41 +00001503 def GetChannelID(self):
1504 """Send a reply containing the hashed ChannelID that the client provided."""
1505
1506 if not self._ShouldHandleRequest('/channel-id'):
1507 return False
1508
1509 self.send_response(200)
1510 self.send_header('Content-Type', 'text/plain')
1511 self.end_headers()
davidben@chromium.org7d53b542014-04-10 17:56:44 +00001512 channel_id = bytes(self.server.tlsConnection.channel_id)
agl@chromium.org04700be2013-03-02 18:40:41 +00001513 self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1514 return True
1515
pneubeckfd4f0442015-08-07 04:55:10 -07001516 def GetClientCert(self):
1517 """Send a reply whether a client certificate was provided."""
1518
1519 if not self._ShouldHandleRequest('/client-cert'):
1520 return False
1521
1522 self.send_response(200)
1523 self.send_header('Content-Type', 'text/plain')
1524 self.end_headers()
1525
1526 cert_chain = self.server.tlsConnection.session.clientCertChain
1527 if cert_chain != None:
1528 self.wfile.write('got client cert with fingerprint: ' +
1529 cert_chain.getFingerprint())
1530 else:
1531 self.wfile.write('got no client cert')
1532 return True
1533
davidben599e7e72014-09-03 16:19:09 -07001534 def ClientCipherListHandler(self):
1535 """Send a reply containing the cipher suite list that the client
1536 provided. Each cipher suite value is serialized in decimal, followed by a
1537 newline."""
1538
1539 if not self._ShouldHandleRequest('/client-cipher-list'):
1540 return False
1541
1542 self.send_response(200)
1543 self.send_header('Content-Type', 'text/plain')
1544 self.end_headers()
1545
davidben11682512014-10-06 21:09:11 -07001546 cipher_suites = self.server.tlsConnection.clientHello.cipher_suites
1547 self.wfile.write('\n'.join(str(c) for c in cipher_suites))
davidben599e7e72014-09-03 16:19:09 -07001548 return True
1549
simonjam@chromium.orgf9cf32f2012-02-13 23:56:14 +00001550 def CloseSocketHandler(self):
1551 """Closes the socket without sending anything."""
1552
1553 if not self._ShouldHandleRequest('/close-socket'):
1554 return False
1555
1556 self.wfile.close()
1557 return True
1558
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001559 def RangeResetHandler(self):
1560 """Send data broken up by connection resets every N (default 4K) bytes.
1561 Support range requests. If the data requested doesn't straddle a reset
1562 boundary, it will all be sent. Used for testing resuming downloads."""
1563
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001564 def DataForRange(start, end):
1565 """Data to be provided for a particular range of bytes."""
1566 # Offset and scale to avoid too obvious (and hence potentially
1567 # collidable) data.
1568 return ''.join([chr(y % 256)
1569 for y in range(start * 2 + 15, end * 2 + 15, 2)])
1570
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001571 if not self._ShouldHandleRequest('/rangereset'):
1572 return False
1573
asanka@chromium.org94670cb2013-11-26 16:57:52 +00001574 # HTTP/1.1 is required for ETag and range support.
1575 self.protocol_version = 'HTTP/1.1'
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001576 _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1577
1578 # Defaults
1579 size = 8000
1580 # Note that the rst is sent just before sending the rst_boundary byte.
1581 rst_boundary = 4000
1582 respond_to_range = True
1583 hold_for_signal = False
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001584 rst_limit = -1
1585 token = 'DEFAULT'
1586 fail_precondition = 0
1587 send_verifiers = True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001588
1589 # Parse the query
1590 qdict = urlparse.parse_qs(query, True)
1591 if 'size' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001592 size = int(qdict['size'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001593 if 'rst_boundary' in qdict:
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001594 rst_boundary = int(qdict['rst_boundary'][0])
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001595 if 'token' in qdict:
1596 # Identifying token for stateful tests.
1597 token = qdict['token'][0]
1598 if 'rst_limit' in qdict:
1599 # Max number of rsts for a given token.
1600 rst_limit = int(qdict['rst_limit'][0])
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001601 if 'bounce_range' in qdict:
1602 respond_to_range = False
1603 if 'hold' in qdict:
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001604 # Note that hold_for_signal will not work with null range requests;
1605 # see TODO below.
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001606 hold_for_signal = True
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001607 if 'no_verifiers' in qdict:
1608 send_verifiers = False
1609 if 'fail_precondition' in qdict:
1610 fail_precondition = int(qdict['fail_precondition'][0])
1611
1612 # Record already set information, or set it.
1613 rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1614 if rst_limit != 0:
1615 TestPageHandler.rst_limits[token] -= 1
1616 fail_precondition = TestPageHandler.fail_precondition.setdefault(
1617 token, fail_precondition)
1618 if fail_precondition != 0:
1619 TestPageHandler.fail_precondition[token] -= 1
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001620
1621 first_byte = 0
1622 last_byte = size - 1
1623
1624 # Does that define what we want to return, or do we need to apply
1625 # a range?
1626 range_response = False
1627 range_header = self.headers.getheader('range')
1628 if range_header and respond_to_range:
1629 mo = re.match("bytes=(\d*)-(\d*)", range_header)
1630 if mo.group(1):
1631 first_byte = int(mo.group(1))
1632 if mo.group(2):
1633 last_byte = int(mo.group(2))
1634 if last_byte > size - 1:
1635 last_byte = size - 1
1636 range_response = True
1637 if last_byte < first_byte:
1638 return False
1639
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001640 if (fail_precondition and
1641 (self.headers.getheader('If-Modified-Since') or
1642 self.headers.getheader('If-Match'))):
1643 self.send_response(412)
1644 self.end_headers()
1645 return True
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001646
1647 if range_response:
1648 self.send_response(206)
1649 self.send_header('Content-Range',
1650 'bytes %d-%d/%d' % (first_byte, last_byte, size))
1651 else:
1652 self.send_response(200)
1653 self.send_header('Content-Type', 'application/octet-stream')
1654 self.send_header('Content-Length', last_byte - first_byte + 1)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001655 if send_verifiers:
asanka@chromium.org3ffced92013-12-13 22:20:27 +00001656 # If fail_precondition is non-zero, then the ETag for each request will be
1657 # different.
1658 etag = "%s%d" % (token, fail_precondition)
1659 self.send_header('ETag', etag)
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001660 self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001661 self.end_headers()
1662
1663 if hold_for_signal:
1664 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1665 # a single byte, the self.server.handle_request() below hangs
1666 # without processing new incoming requests.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001667 self.wfile.write(DataForRange(first_byte, first_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001668 first_byte = first_byte + 1
1669 # handle requests until one of them clears this flag.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001670 self.server.wait_for_download = True
1671 while self.server.wait_for_download:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001672 self.server.handle_request()
1673
1674 possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001675 if possible_rst >= last_byte or rst_limit == 0:
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001676 # No RST has been requested in this range, so we don't need to
1677 # do anything fancy; just write the data and let the python
1678 # infrastructure close the connection.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001679 self.wfile.write(DataForRange(first_byte, last_byte + 1))
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001680 self.wfile.flush()
1681 return True
1682
1683 # We're resetting the connection part way in; go to the RST
1684 # boundary and then send an RST.
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001685 # Because socket semantics do not guarantee that all the data will be
1686 # sent when using the linger semantics to hard close a socket,
1687 # we send the data and then wait for our peer to release us
1688 # before sending the reset.
rdsmith@chromium.org801f9a62013-03-16 09:35:19 +00001689 data = DataForRange(first_byte, possible_rst)
1690 self.wfile.write(data)
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001691 self.wfile.flush()
rdsmith@chromium.orgd1c45532013-01-22 19:20:29 +00001692 self.server.wait_for_download = True
1693 while self.server.wait_for_download:
1694 self.server.handle_request()
rdsmith@chromium.org4abbbcd2013-01-12 15:57:10 +00001695 l_onoff = 1 # Linger is active.
1696 l_linger = 0 # Seconds to linger for.
1697 self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1698 struct.pack('ii', l_onoff, l_linger))
1699
1700 # Close all duplicates of the underlying socket to force the RST.
1701 self.wfile.close()
1702 self.rfile.close()
1703 self.connection.close()
1704
1705 return True
1706
initial.commit94958cf2008-07-26 22:42:52 +00001707 def DefaultResponseHandler(self):
1708 """This is the catch-all response handler for requests that aren't handled
1709 by one of the special handlers above.
1710 Note that we specify the content-length as without it the https connection
1711 is not closed properly (and the browser keeps expecting data)."""
1712
1713 contents = "Default response given for path: " + self.path
1714 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001715 self.send_header('Content-Type', 'text/html')
1716 self.send_header('Content-Length', len(contents))
initial.commit94958cf2008-07-26 22:42:52 +00001717 self.end_headers()
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001718 if (self.command != 'HEAD'):
1719 self.wfile.write(contents)
initial.commit94958cf2008-07-26 22:42:52 +00001720 return True
1721
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001722 def RedirectConnectHandler(self):
1723 """Sends a redirect to the CONNECT request for www.redirect.com. This
1724 response is not specified by the RFC, so the browser should not follow
1725 the redirect."""
1726
1727 if (self.path.find("www.redirect.com") < 0):
1728 return False
1729
1730 dest = "http://www.destination.com/foo.js"
1731
1732 self.send_response(302) # moved temporarily
1733 self.send_header('Location', dest)
1734 self.send_header('Connection', 'close')
1735 self.end_headers()
1736 return True
1737
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +00001738 def ServerAuthConnectHandler(self):
1739 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1740 response doesn't make sense because the proxy server cannot request
1741 server authentication."""
1742
1743 if (self.path.find("www.server-auth.com") < 0):
1744 return False
1745
1746 challenge = 'Basic realm="WallyWorld"'
1747
1748 self.send_response(401) # unauthorized
1749 self.send_header('WWW-Authenticate', challenge)
1750 self.send_header('Connection', 'close')
1751 self.end_headers()
1752 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001753
1754 def DefaultConnectResponseHandler(self):
1755 """This is the catch-all response handler for CONNECT requests that aren't
1756 handled by one of the special handlers above. Real Web servers respond
1757 with 400 to CONNECT requests."""
1758
1759 contents = "Your client has issued a malformed or illegal request."
1760 self.send_response(400) # bad request
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001761 self.send_header('Content-Type', 'text/html')
1762 self.send_header('Content-Length', len(contents))
wtc@chromium.org743d77b2009-02-11 02:48:15 +00001763 self.end_headers()
1764 self.wfile.write(contents)
1765 return True
1766
initial.commit94958cf2008-07-26 22:42:52 +00001767 # called by the redirect handling function when there is no parameter
1768 def sendRedirectHelp(self, redirect_name):
1769 self.send_response(200)
mmenke@chromium.orgbfff75b2011-11-01 02:32:05 +00001770 self.send_header('Content-Type', 'text/html')
initial.commit94958cf2008-07-26 22:42:52 +00001771 self.end_headers()
1772 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1773 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1774 self.wfile.write('</body></html>')
1775
vsevik@chromium.orgf0e997e2011-05-20 09:36:14 +00001776 # called by chunked handling function
1777 def sendChunkHelp(self, chunk):
1778 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1779 self.wfile.write('%X\r\n' % len(chunk))
1780 self.wfile.write(chunk)
1781 self.wfile.write('\r\n')
1782
akalin@chromium.org154bb132010-11-12 02:20:27 +00001783
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001784class OCSPHandler(testserver_base.BasePageHandler):
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001785 def __init__(self, request, client_address, socket_server):
1786 handlers = [self.OCSPResponse]
1787 self.ocsp_response = socket_server.ocsp_response
rsimha@chromium.org99a6f172013-01-20 01:10:24 +00001788 testserver_base.BasePageHandler.__init__(self, request, client_address,
1789 socket_server, [], handlers, [],
1790 handlers, [])
agl@chromium.org77a9ad92012-03-20 15:14:27 +00001791
1792 def OCSPResponse(self):
1793 self.send_response(200)
1794 self.send_header('Content-Type', 'application/ocsp-response')
1795 self.send_header('Content-Length', str(len(self.ocsp_response)))
1796 self.end_headers()
1797
1798 self.wfile.write(self.ocsp_response)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001799
mattm@chromium.org830a3712012-11-07 23:00:07 +00001800
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001801class TCPEchoHandler(SocketServer.BaseRequestHandler):
1802 """The RequestHandler class for TCP echo server.
1803
1804 It is instantiated once per connection to the server, and overrides the
1805 handle() method to implement communication to the client.
1806 """
1807
1808 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001809 """Handles the request from the client and constructs a response."""
1810
1811 data = self.request.recv(65536).strip()
1812 # Verify the "echo request" message received from the client. Send back
1813 # "echo response" message if "echo request" message is valid.
1814 try:
1815 return_data = echo_message.GetEchoResponseData(data)
1816 if not return_data:
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001817 return
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001818 except ValueError:
1819 return
1820
1821 self.request.send(return_data)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001822
1823
1824class UDPEchoHandler(SocketServer.BaseRequestHandler):
1825 """The RequestHandler class for UDP echo server.
1826
1827 It is instantiated once per connection to the server, and overrides the
1828 handle() method to implement communication to the client.
1829 """
1830
1831 def handle(self):
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001832 """Handles the request from the client and constructs a response."""
1833
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001834 data = self.request[0].strip()
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001835 request_socket = self.request[1]
rtenneti@chromium.org922a8222011-08-16 03:30:45 +00001836 # Verify the "echo request" message received from the client. Send back
1837 # "echo response" message if "echo request" message is valid.
1838 try:
1839 return_data = echo_message.GetEchoResponseData(data)
1840 if not return_data:
1841 return
1842 except ValueError:
1843 return
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001844 request_socket.sendto(return_data, self.client_address)
rtenneti@chromium.orgfc70e5e2011-06-09 05:11:41 +00001845
1846
bashi@chromium.org33233532012-09-08 17:37:24 +00001847class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1848 """A request handler that behaves as a proxy server which requires
1849 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1850 """
1851
1852 _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1853
1854 def parse_request(self):
1855 """Overrides parse_request to check credential."""
1856
1857 if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1858 return False
1859
1860 auth = self.headers.getheader('Proxy-Authorization')
1861 if auth != self._AUTH_CREDENTIAL:
1862 self.send_response(407)
1863 self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1864 self.end_headers()
1865 return False
1866
1867 return True
1868
1869 def _start_read_write(self, sock):
1870 sock.setblocking(0)
1871 self.request.setblocking(0)
1872 rlist = [self.request, sock]
1873 while True:
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001874 ready_sockets, _unused, errors = select.select(rlist, [], [])
bashi@chromium.org33233532012-09-08 17:37:24 +00001875 if errors:
1876 self.send_response(500)
1877 self.end_headers()
1878 return
1879 for s in ready_sockets:
1880 received = s.recv(1024)
1881 if len(received) == 0:
1882 return
1883 if s == self.request:
1884 other = sock
1885 else:
1886 other = self.request
1887 other.send(received)
1888
1889 def _do_common_method(self):
1890 url = urlparse.urlparse(self.path)
1891 port = url.port
1892 if not port:
1893 if url.scheme == 'http':
1894 port = 80
1895 elif url.scheme == 'https':
1896 port = 443
1897 if not url.hostname or not port:
1898 self.send_response(400)
1899 self.end_headers()
1900 return
1901
1902 if len(url.path) == 0:
1903 path = '/'
1904 else:
1905 path = url.path
1906 if len(url.query) > 0:
1907 path = '%s?%s' % (url.path, url.query)
1908
1909 sock = None
1910 try:
1911 sock = socket.create_connection((url.hostname, port))
1912 sock.send('%s %s %s\r\n' % (
1913 self.command, path, self.protocol_version))
1914 for header in self.headers.headers:
1915 header = header.strip()
1916 if (header.lower().startswith('connection') or
1917 header.lower().startswith('proxy')):
1918 continue
1919 sock.send('%s\r\n' % header)
1920 sock.send('\r\n')
1921 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001922 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001923 self.send_response(500)
1924 self.end_headers()
1925 finally:
1926 if sock is not None:
1927 sock.close()
1928
1929 def do_CONNECT(self):
1930 try:
1931 pos = self.path.rfind(':')
1932 host = self.path[:pos]
1933 port = int(self.path[pos+1:])
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001934 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001935 self.send_response(400)
1936 self.end_headers()
1937
1938 try:
1939 sock = socket.create_connection((host, port))
1940 self.send_response(200, 'Connection established')
1941 self.end_headers()
1942 self._start_read_write(sock)
toyoshim@chromium.org9d7219e2012-10-25 03:30:10 +00001943 except Exception:
bashi@chromium.org33233532012-09-08 17:37:24 +00001944 self.send_response(500)
1945 self.end_headers()
1946 finally:
1947 sock.close()
1948
1949 def do_GET(self):
1950 self._do_common_method()
1951
1952 def do_HEAD(self):
1953 self._do_common_method()
1954
1955
mattm@chromium.org830a3712012-11-07 23:00:07 +00001956class ServerRunner(testserver_base.TestServerRunner):
1957 """TestServerRunner for the net test servers."""
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001958
mattm@chromium.org830a3712012-11-07 23:00:07 +00001959 def __init__(self):
1960 super(ServerRunner, self).__init__()
1961 self.__ocsp_server = None
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001962
mattm@chromium.org830a3712012-11-07 23:00:07 +00001963 def __make_data_dir(self):
1964 if self.options.data_dir:
1965 if not os.path.isdir(self.options.data_dir):
1966 raise testserver_base.OptionError('specified data dir not found: ' +
1967 self.options.data_dir + ' exiting...')
1968 my_data_dir = self.options.data_dir
1969 else:
1970 # Create the default path to our data dir, relative to the exe dir.
1971 my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1972 "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001973
mattm@chromium.org830a3712012-11-07 23:00:07 +00001974 #TODO(ibrar): Must use Find* funtion defined in google\tools
1975 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
phajdan.jr@chromium.orgbf74e2b2010-08-17 20:07:11 +00001976
mattm@chromium.org830a3712012-11-07 23:00:07 +00001977 return my_data_dir
newt@chromium.org1fc32742012-10-20 00:28:35 +00001978
mattm@chromium.org830a3712012-11-07 23:00:07 +00001979 def create_server(self, server_data):
1980 port = self.options.port
1981 host = self.options.host
newt@chromium.org1fc32742012-10-20 00:28:35 +00001982
estark21667d62015-04-08 21:00:16 -07001983 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1984 # will result in a call to |getaddrinfo|, which fails with "nodename
1985 # nor servname provided" for localhost:0 on 10.6.
1986 if self.options.server_type == SERVER_WEBSOCKET and \
1987 host == "localhost" and \
1988 port == 0:
1989 host = "127.0.0.1"
1990
mattm@chromium.org830a3712012-11-07 23:00:07 +00001991 if self.options.server_type == SERVER_HTTP:
1992 if self.options.https:
1993 pem_cert_and_key = None
davidben3e2564a2014-11-07 18:51:00 -08001994 ocsp_der = None
mattm@chromium.org830a3712012-11-07 23:00:07 +00001995 if self.options.cert_and_key_file:
1996 if not os.path.isfile(self.options.cert_and_key_file):
1997 raise testserver_base.OptionError(
1998 'specified server cert file not found: ' +
1999 self.options.cert_and_key_file + ' exiting...')
2000 pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
thakis@chromium.org408ea8f2012-11-07 06:57:04 +00002001 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002002 # generate a new certificate and run an OCSP server for it.
2003 self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002004 print ('OCSP server started on %s:%d...' %
mattm@chromium.org830a3712012-11-07 23:00:07 +00002005 (host, self.__ocsp_server.server_port))
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002006
mattm@chromium.org830a3712012-11-07 23:00:07 +00002007 ocsp_state = None
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002008
mattm@chromium.org830a3712012-11-07 23:00:07 +00002009 if self.options.ocsp == 'ok':
2010 ocsp_state = minica.OCSP_STATE_GOOD
2011 elif self.options.ocsp == 'revoked':
2012 ocsp_state = minica.OCSP_STATE_REVOKED
2013 elif self.options.ocsp == 'invalid':
2014 ocsp_state = minica.OCSP_STATE_INVALID
2015 elif self.options.ocsp == 'unauthorized':
2016 ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
2017 elif self.options.ocsp == 'unknown':
2018 ocsp_state = minica.OCSP_STATE_UNKNOWN
2019 else:
2020 raise testserver_base.OptionError('unknown OCSP status: ' +
2021 self.options.ocsp_status)
mattm@chromium.orgdeed82b2012-11-07 04:36:07 +00002022
mattm@chromium.org830a3712012-11-07 23:00:07 +00002023 (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
2024 subject = "127.0.0.1",
2025 ocsp_url = ("http://%s:%d/ocsp" %
2026 (host, self.__ocsp_server.server_port)),
agl@chromium.orgdf778142013-07-31 21:57:28 +00002027 ocsp_state = ocsp_state,
2028 serial = self.options.cert_serial)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002029
davidben3e2564a2014-11-07 18:51:00 -08002030 if self.options.ocsp_server_unavailable:
2031 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2032 self.__ocsp_server.ocsp_response = '30030a0103'.decode('hex')
2033 else:
2034 self.__ocsp_server.ocsp_response = ocsp_der
mattm@chromium.org830a3712012-11-07 23:00:07 +00002035
2036 for ca_cert in self.options.ssl_client_ca:
2037 if not os.path.isfile(ca_cert):
2038 raise testserver_base.OptionError(
2039 'specified trusted client CA file not found: ' + ca_cert +
2040 ' exiting...')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002041
2042 stapled_ocsp_response = None
davidben3e2564a2014-11-07 18:51:00 -08002043 if self.options.staple_ocsp_response:
2044 stapled_ocsp_response = ocsp_der
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002045
mattm@chromium.org830a3712012-11-07 23:00:07 +00002046 server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
2047 self.options.ssl_client_auth,
2048 self.options.ssl_client_ca,
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002049 self.options.ssl_client_cert_type,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002050 self.options.ssl_bulk_cipher,
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002051 self.options.ssl_key_exchange,
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002052 self.options.enable_npn,
mattm@chromium.org830a3712012-11-07 23:00:07 +00002053 self.options.record_resume,
ekasper@google.com24aa8222013-11-28 13:43:26 +00002054 self.options.tls_intolerant,
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002055 self.options.tls_intolerance_type,
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002056 self.options.signed_cert_timestamps_tls_ext.decode(
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002057 "base64"),
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002058 self.options.fallback_scsv,
davidben21cda342015-03-17 18:04:28 -07002059 stapled_ocsp_response,
2060 self.options.alert_after_handshake)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002061 print 'HTTPS server started on https://%s:%d...' % \
2062 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002063 else:
2064 server = HTTPServer((host, port), TestPageHandler)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002065 print 'HTTP server started on http://%s:%d...' % \
2066 (host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002067
2068 server.data_dir = self.__make_data_dir()
2069 server.file_root_url = self.options.file_root_url
2070 server_data['port'] = server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002071 elif self.options.server_type == SERVER_WEBSOCKET:
2072 # Launch pywebsocket via WebSocketServer.
2073 logger = logging.getLogger()
2074 logger.addHandler(logging.StreamHandler())
2075 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2076 # is required to work correctly. It should be fixed from pywebsocket side.
2077 os.chdir(self.__make_data_dir())
2078 websocket_options = WebSocketOptions(host, port, '.')
davidben@chromium.org009843a2014-06-03 00:13:08 +00002079 scheme = "ws"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002080 if self.options.cert_and_key_file:
davidben@chromium.org009843a2014-06-03 00:13:08 +00002081 scheme = "wss"
mattm@chromium.org830a3712012-11-07 23:00:07 +00002082 websocket_options.use_tls = True
2083 websocket_options.private_key = self.options.cert_and_key_file
2084 websocket_options.certificate = self.options.cert_and_key_file
2085 if self.options.ssl_client_auth:
pneubeck@chromium.orgf5007112014-07-21 15:22:41 +00002086 websocket_options.tls_client_cert_optional = False
mattm@chromium.org830a3712012-11-07 23:00:07 +00002087 websocket_options.tls_client_auth = True
2088 if len(self.options.ssl_client_ca) != 1:
2089 raise testserver_base.OptionError(
2090 'one trusted client CA file should be specified')
2091 if not os.path.isfile(self.options.ssl_client_ca[0]):
2092 raise testserver_base.OptionError(
2093 'specified trusted client CA file not found: ' +
2094 self.options.ssl_client_ca[0] + ' exiting...')
2095 websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
estark21667d62015-04-08 21:00:16 -07002096 print 'Trying to start websocket server on %s://%s:%d...' % \
2097 (scheme, websocket_options.server_host, websocket_options.port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002098 server = WebSocketServer(websocket_options)
davidben@chromium.org009843a2014-06-03 00:13:08 +00002099 print 'WebSocket server started on %s://%s:%d...' % \
2100 (scheme, host, server.server_port)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002101 server_data['port'] = server.server_port
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002102 websocket_options.use_basic_auth = self.options.ws_basic_auth
mattm@chromium.org830a3712012-11-07 23:00:07 +00002103 elif self.options.server_type == SERVER_TCP_ECHO:
2104 # Used for generating the key (randomly) that encodes the "echo request"
2105 # message.
2106 random.seed()
2107 server = TCPEchoServer((host, port), TCPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002108 print 'Echo TCP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002109 server_data['port'] = server.server_port
2110 elif self.options.server_type == SERVER_UDP_ECHO:
2111 # Used for generating the key (randomly) that encodes the "echo request"
2112 # message.
2113 random.seed()
2114 server = UDPEchoServer((host, port), UDPEchoHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002115 print 'Echo UDP server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002116 server_data['port'] = server.server_port
2117 elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
2118 server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002119 print 'BasicAuthProxy server started on port %d...' % server.server_port
mattm@chromium.org830a3712012-11-07 23:00:07 +00002120 server_data['port'] = server.server_port
2121 elif self.options.server_type == SERVER_FTP:
2122 my_data_dir = self.__make_data_dir()
2123
2124 # Instantiate a dummy authorizer for managing 'virtual' users
2125 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
2126
xleng9d4c45f2015-05-04 16:26:12 -07002127 # Define a new user having full r/w permissions
mattm@chromium.org830a3712012-11-07 23:00:07 +00002128 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
2129
xleng9d4c45f2015-05-04 16:26:12 -07002130 # Define a read-only anonymous user unless disabled
2131 if not self.options.no_anonymous_ftp_user:
2132 authorizer.add_anonymous(my_data_dir)
mattm@chromium.org830a3712012-11-07 23:00:07 +00002133
2134 # Instantiate FTP handler class
2135 ftp_handler = pyftpdlib.ftpserver.FTPHandler
2136 ftp_handler.authorizer = authorizer
2137
2138 # Define a customized banner (string returned when client connects)
2139 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
2140 pyftpdlib.ftpserver.__ver__)
2141
2142 # Instantiate FTP server class and listen to address:port
2143 server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
2144 server_data['port'] = server.socket.getsockname()[1]
wjia@chromium.orgff532f32013-03-18 19:23:44 +00002145 print 'FTP server started on port %d...' % server_data['port']
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002146 else:
mattm@chromium.org830a3712012-11-07 23:00:07 +00002147 raise testserver_base.OptionError('unknown server type' +
2148 self.options.server_type)
erikkay@google.com70397b62008-12-30 21:49:21 +00002149
mattm@chromium.org830a3712012-11-07 23:00:07 +00002150 return server
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002151
mattm@chromium.org830a3712012-11-07 23:00:07 +00002152 def run_server(self):
2153 if self.__ocsp_server:
2154 self.__ocsp_server.serve_forever_on_thread()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002155
mattm@chromium.org830a3712012-11-07 23:00:07 +00002156 testserver_base.TestServerRunner.run_server(self)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002157
mattm@chromium.org830a3712012-11-07 23:00:07 +00002158 if self.__ocsp_server:
2159 self.__ocsp_server.stop_serving()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002160
mattm@chromium.org830a3712012-11-07 23:00:07 +00002161 def add_options(self):
2162 testserver_base.TestServerRunner.add_options(self)
2163 self.option_parser.add_option('-f', '--ftp', action='store_const',
2164 const=SERVER_FTP, default=SERVER_HTTP,
2165 dest='server_type',
2166 help='start up an FTP server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002167 self.option_parser.add_option('--tcp-echo', action='store_const',
2168 const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2169 dest='server_type',
2170 help='start up a tcp echo server.')
2171 self.option_parser.add_option('--udp-echo', action='store_const',
2172 const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2173 dest='server_type',
2174 help='start up a udp echo server.')
2175 self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2176 const=SERVER_BASIC_AUTH_PROXY,
2177 default=SERVER_HTTP, dest='server_type',
2178 help='start up a proxy server which requires '
2179 'basic authentication.')
2180 self.option_parser.add_option('--websocket', action='store_const',
2181 const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2182 dest='server_type',
2183 help='start up a WebSocket server.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002184 self.option_parser.add_option('--https', action='store_true',
2185 dest='https', help='Specify that https '
2186 'should be used.')
2187 self.option_parser.add_option('--cert-and-key-file',
2188 dest='cert_and_key_file', help='specify the '
2189 'path to the file containing the certificate '
2190 'and private key for the server in PEM '
2191 'format')
2192 self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2193 help='The type of OCSP response generated '
2194 'for the automatically generated '
2195 'certificate. One of [ok,revoked,invalid]')
agl@chromium.orgdf778142013-07-31 21:57:28 +00002196 self.option_parser.add_option('--cert-serial', dest='cert_serial',
2197 default=0, type=int,
2198 help='If non-zero then the generated '
2199 'certificate will have this serial number')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002200 self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2201 default='0', type='int',
2202 help='If nonzero, certain TLS connections '
2203 'will be aborted in order to test version '
2204 'fallback. 1 means all TLS versions will be '
2205 'aborted. 2 means TLS 1.1 or higher will be '
2206 'aborted. 3 means TLS 1.2 or higher will be '
2207 'aborted.')
davidben@chromium.orgbbf4f402014-06-27 01:16:55 +00002208 self.option_parser.add_option('--tls-intolerance-type',
2209 dest='tls_intolerance_type',
2210 default="alert",
2211 help='Controls how the server reacts to a '
2212 'TLS version it is intolerant to. Valid '
2213 'values are "alert", "close", and "reset".')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002214 self.option_parser.add_option('--signed-cert-timestamps-tls-ext',
2215 dest='signed_cert_timestamps_tls_ext',
ekasper@google.com24aa8222013-11-28 13:43:26 +00002216 default='',
2217 help='Base64 encoded SCT list. If set, '
2218 'server will respond with a '
2219 'signed_certificate_timestamp TLS extension '
2220 'whenever the client supports it.')
agl@chromium.orgd0e11ca2013-12-11 20:16:13 +00002221 self.option_parser.add_option('--fallback-scsv', dest='fallback_scsv',
2222 default=False, const=True,
2223 action='store_const',
2224 help='If given, TLS_FALLBACK_SCSV support '
2225 'will be enabled. This causes the server to '
2226 'reject fallback connections from compatible '
2227 'clients (e.g. Chrome).')
ekasper@google.com3bce2cf2013-12-17 00:25:51 +00002228 self.option_parser.add_option('--staple-ocsp-response',
2229 dest='staple_ocsp_response',
2230 default=False, action='store_true',
2231 help='If set, server will staple the OCSP '
2232 'response whenever OCSP is on and the client '
2233 'supports OCSP stapling.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002234 self.option_parser.add_option('--https-record-resume',
2235 dest='record_resume', const=True,
2236 default=False, action='store_const',
2237 help='Record resumption cache events rather '
2238 'than resuming as normal. Allows the use of '
2239 'the /ssl-session-cache request')
2240 self.option_parser.add_option('--ssl-client-auth', action='store_true',
2241 help='Require SSL client auth on every '
2242 'connection.')
2243 self.option_parser.add_option('--ssl-client-ca', action='append',
2244 default=[], help='Specify that the client '
2245 'certificate request should include the CA '
2246 'named in the subject of the DER-encoded '
2247 'certificate contained in the specified '
2248 'file. This option may appear multiple '
2249 'times, indicating multiple CA names should '
2250 'be sent in the request.')
davidben@chromium.orgc52e2e62014-05-20 21:51:44 +00002251 self.option_parser.add_option('--ssl-client-cert-type', action='append',
2252 default=[], help='Specify that the client '
2253 'certificate request should include the '
2254 'specified certificate_type value. This '
2255 'option may appear multiple times, '
2256 'indicating multiple values should be send '
2257 'in the request. Valid values are '
2258 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2259 'If omitted, "rsa_sign" will be used.')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002260 self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2261 help='Specify the bulk encryption '
2262 'algorithm(s) that will be accepted by the '
davidben26254762015-01-29 14:32:53 -08002263 'SSL server. Valid values are "aes128gcm", '
2264 '"aes256", "aes128", "3des", "rc4". If '
2265 'omitted, all algorithms will be used. This '
2266 'option may appear multiple times, '
2267 'indicating multiple algorithms should be '
2268 'enabled.');
davidben@chromium.org74aa8dd2014-04-11 07:20:26 +00002269 self.option_parser.add_option('--ssl-key-exchange', action='append',
2270 help='Specify the key exchange algorithm(s)'
2271 'that will be accepted by the SSL server. '
davidben405745f2015-04-03 11:35:35 -07002272 'Valid values are "rsa", "dhe_rsa", '
2273 '"ecdhe_rsa". If omitted, all algorithms '
2274 'will be used. This option may appear '
2275 'multiple times, indicating multiple '
2276 'algorithms should be enabled.');
davidben@chromium.org5e0a9dd2014-04-16 23:58:20 +00002277 # TODO(davidben): Add ALPN support to tlslite.
2278 self.option_parser.add_option('--enable-npn', dest='enable_npn',
2279 default=False, const=True,
2280 action='store_const',
2281 help='Enable server support for the NPN '
2282 'extension. The server will advertise '
2283 'support for exactly one protocol, http/1.1')
mattm@chromium.org830a3712012-11-07 23:00:07 +00002284 self.option_parser.add_option('--file-root-url', default='/files/',
2285 help='Specify a root URL for files served.')
ricea@chromium.orga52ebdc2014-07-29 07:42:29 +00002286 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2287 self.option_parser.add_option('--ws-basic-auth', action='store_true',
2288 dest='ws_basic_auth',
2289 help='Enable basic-auth for WebSocket')
davidben3e2564a2014-11-07 18:51:00 -08002290 self.option_parser.add_option('--ocsp-server-unavailable',
2291 dest='ocsp_server_unavailable',
2292 default=False, action='store_true',
2293 help='If set, the OCSP server will return '
2294 'a tryLater status rather than the actual '
2295 'OCSP response.')
davidben21cda342015-03-17 18:04:28 -07002296 self.option_parser.add_option('--alert-after-handshake',
2297 dest='alert_after_handshake',
2298 default=False, action='store_true',
2299 help='If set, the server will send a fatal '
2300 'alert immediately after the handshake.')
xleng9d4c45f2015-05-04 16:26:12 -07002301 self.option_parser.add_option('--no-anonymous-ftp-user',
2302 dest='no_anonymous_ftp_user',
2303 default=False, action='store_true',
2304 help='If set, the FTP server will not create '
2305 'an anonymous user.')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00002306
toyoshim@chromium.org16f57522012-10-24 06:14:38 +00002307
initial.commit94958cf2008-07-26 22:42:52 +00002308if __name__ == '__main__':
mattm@chromium.org830a3712012-11-07 23:00:07 +00002309 sys.exit(ServerRunner().main())